Problem/Motivation

See #3019487: [plan] Describe layout builder UI to assistive technology

Proposed resolution

Idea 1 - ARIA group role(s) with associated label

This enables a screen reader user to understand what sort of group they have entered, when they are traversing the document using the screen reader in normal browse/read mode, or tabbing through interactive controls.

  • Add <div role="group" aria-labelledby=""> grouping wrappers, to name the available sections and regions. Each layout region, and each layout section, gets a group role.
  • The accessible name could derive from the *.layouts.yml layout plugin definitions. There are human-readable names for the layout itself (2 column 25%/75%), and the regions inside it (left, right).
  • Sections don't have names, so we'll have to describe these using their number (position-in-set) to distinguish them, i.e. "Section 2."
  • The layout plugin's name could also be used, but it may not be unique so we'd append it to the numbered section label, e.g.. "Section 2 - two-column layout 25%/75%". Not sure if that belongs in the section's accessible name, or an accessible description (see idea 2)
  • Which ARIA container role should be used? Group, section, and region are all possibilities. Note that "region" means a landmark region, and "section" is equivalent to the HTML5 <section> element.

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tim.plunkett created an issue. See original summary.

tim.plunkett’s picture

Status: Active » Needs review
FileSize
1.32 KB

Before:

<div id="layout-builder" class="layout-builder">
  <div class="layout-builder__add-section"><a href="/layout_builder/choose/section/overrides/node.1/0" class="use-ajax layout-builder__link layout-builder__link--add" data-dialog-type="dialog" data-dialog-renderer="off_canvas" tabindex="-1">Add Section <span class="visually-hidden">at start of layout</span></a></div>
  <div class="layout-builder__section" role="group" aria-labelledby="Section 1">
    <a href="/layout_builder/remove/section/overrides/node.1/0" class="use-ajax layout-builder__link layout-builder__link--remove" data-dialog-type="dialog" data-dialog-renderer="off_canvas" tabindex="-1">Remove section <span class="visually-hidden">1</span></a>
    <a href="/layout_builder/configure/section/overrides/node.1/0" class="use-ajax layout-builder__link layout-builder__link--configure" data-dialog-type="dialog" data-dialog-renderer="off_canvas" tabindex="-1">Configure section <span class="visually-hidden">1</span></a>
      <div class="layout layout--threecol-section layout--threecol-section--25-50-25 layout-builder__layout" data-layout-update-url="/layout_builder/move/block/overrides/node.1" data-layout-delta="0">
        <div data-region="first" class="layout-builder__region layout__region layout__region--first ui-sortable" role="group" aria-labelledby="First region in section 1">
          <div class="layout-builder__add-block">
            <a href="/layout_builder/choose/block/overrides/node.1/0/first" class="use-ajax layout-builder__link layout-builder__link--add" data-dialog-type="dialog" data-dialog-renderer="off_canvas" tabindex="-1">Add Block <span class="visually-hidden">in section 1, First region</span></a>
          </div>
        </div>
        <div data-region="second" class="layout-builder__region layout__region layout__region--second ui-sortable" role="group" aria-labelledby="Second region in section 1">
          <div class="layout-builder__add-block">
            <a href="/layout_builder/choose/block/overrides/node.1/0/second" class="use-ajax layout-builder__link layout-builder__link--add" data-dialog-type="dialog" data-dialog-renderer="off_canvas" tabindex="-1">Add Block <span class="visually-hidden">in section 1, Second region</span></a>
          </div>
        </div>
        <div data-region="third" class="layout-builder__region layout__region layout__region--third ui-sortable" role="group" aria-labelledby="Third region in section 1">
          <div class="layout-builder__add-block">
            <a href="/layout_builder/choose/block/overrides/node.1/0/third" class="use-ajax layout-builder__link layout-builder__link--add" data-dialog-type="dialog" data-dialog-renderer="off_canvas" tabindex="-1">Add Block <span class="visually-hidden">in section 1, Third region</span></a>
          </div>
        </div>
    </div>
  </div>
</div>

After:

<div id="layout-builder" class="layout-builder">
  <div class="layout-builder__add-section"><a href="/layout_builder/choose/section/overrides/node.1/0" class="use-ajax layout-builder__link layout-builder__link--add" data-dialog-type="dialog" data-dialog-renderer="off_canvas" tabindex="-1">Add Section <span class="visually-hidden">at start of layout</span></a></div>
  <div class="layout-builder__section">
    <a href="/layout_builder/remove/section/overrides/node.1/0" class="use-ajax layout-builder__link layout-builder__link--remove" data-dialog-type="dialog" data-dialog-renderer="off_canvas" tabindex="-1">Remove section <span class="visually-hidden">1</span></a>
    <a href="/layout_builder/configure/section/overrides/node.1/0" class="use-ajax layout-builder__link layout-builder__link--configure" data-dialog-type="dialog" data-dialog-renderer="off_canvas" tabindex="-1">Configure section <span class="visually-hidden">1</span></a>
      <div class="layout layout--threecol-section layout--threecol-section--25-50-25 layout-builder__layout" data-layout-update-url="/layout_builder/move/block/overrides/node.1" data-layout-delta="0">
        <div data-region="first" class="layout-builder__region layout__region layout__region--first ui-sortable">
          <div class="layout-builder__add-block">
            <a href="/layout_builder/choose/block/overrides/node.1/0/first" class="use-ajax layout-builder__link layout-builder__link--add" data-dialog-type="dialog" data-dialog-renderer="off_canvas" tabindex="-1">Add Block <span class="visually-hidden">in section 1, First region</span></a>
          </div>
        </div>
        <div data-region="second" class="layout-builder__region layout__region layout__region--second ui-sortable">
          <div class="layout-builder__add-block">
            <a href="/layout_builder/choose/block/overrides/node.1/0/second" class="use-ajax layout-builder__link layout-builder__link--add" data-dialog-type="dialog" data-dialog-renderer="off_canvas" tabindex="-1">Add Block <span class="visually-hidden">in section 1, Second region</span></a>
          </div>
        </div>
        <div data-region="third" class="layout-builder__region layout__region layout__region--third ui-sortable">
          <div class="layout-builder__add-block">
            <a href="/layout_builder/choose/block/overrides/node.1/0/third" class="use-ajax layout-builder__link layout-builder__link--add" data-dialog-type="dialog" data-dialog-renderer="off_canvas" tabindex="-1">Add Block <span class="visually-hidden">in section 1, Third region</span></a>
          </div>
        </div>
    </div>
  </div>
</div>
cboyden’s picture

Status: Needs review » Needs work

The aria-labelledby attribute is supposed to contain the ID of the element that contains the label. If the label is visible on the screen somewhere, continue using aria-labelledby but fill it with the ID of the labeling element, as in <div aria-labelledby="section-1-label"> or some such. If the text is not visible on the screen, use <div aria-label="Section 1">.

cboyden’s picture

Status: Needs work » Needs review
FileSize
1.31 KB

Updated patch to use aria-label. If the label is visible on the screen, this approach isn't the best. In that case the code will need to know (or generate) an ID for the element that contains the label.

tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community

In this case, the label is not ever visible on the screen.
I misunderstood the usage of labelledby, but now it makes perfect sense.
Thanks @cboyden!

xjm’s picture

Status: Reviewed & tested by the community » Needs review
Issue tags: +Needs tests

Shouldn't this have test coverage?

tim.plunkett’s picture

phenaproxima’s picture

Status: Needs review » Needs work
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
@@ -328,6 +328,16 @@ public function testLayoutBuilderUi() {
+    $this->assertEquals($expected_labels, $labels);

Wasn't going to raise this, but discussed briefly with @tim.plunkett in Slack. Apparently we do want to test that the order of both arrays is the same, so this needs to be assertSame().

That's it, though. RTBC after that.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
2.21 KB
658 bytes

Switching to a strict comparison just in case

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

Okay. RTBC assuming the test passes.

The last submitted patch, 7: 3041143-aria-7-FAIL.patch, failed testing. View results

xjm’s picture

This looks good to me. I think it would probably be good to confirm with @andrewmacpherson that this implements the most important item of #3019487: [plan] Describe layout builder UI to assistive technology but otherwise I think this is ready for commit after the commit freeze.

rainbreaw’s picture

Just noting here that I promised on the #ux meeting that I would take a look at this. I will be doing so my tomorrow AM (Wed AM PT).

rainbreaw’s picture

Hi all, I did a test of this using screen reader + keyboard only (no eyes, mouse, or trackpad), and it worked pretty well.

  • You know you are inside the layout builder when you enter it
  • The section # is announced when you are in a section
  • When on an add section button between sections, you know the section #s you are between
  • When you are on the remove link for a section, you hear the section # you are looking to remove
  • When you are on the contextual links for a section, you hear the section # for those contextual links

I think there is probably further work that could be done to make this even better, but it is usable. The only frustration was already raised on the UX call and is a separate issue: when you close the side panel, your focus is sent back to the save layout button at the top instead of where you just where.

Another separate (somewhat subjective) accessibility issue that perhaps merits its own entry and prioritization for future work: the Save Layout and Discard Changes buttons are only at the top of the custom layout, which means that the user will have to backwards tab all the way to the beginning of the customizable section in order to save their layout. I would suggest replicating these buttons at the bottom of the customizable layout area, as well, or providing the keyboard user some other means of getting to these buttons quickly once they have reached the final section of their layout.

tim.plunkett’s picture

Thanks so much @rainbreaw!
Tagging for the followup, assigning credit

  • xjm committed 9985cc0 on 8.8.x
    Issue #3041143 by tim.plunkett, cboyden, xjm, phenaproxima, rainbreaw:...
xjm’s picture

Version: 8.8.x-dev » 8.7.x-dev

Awesome, thanks @rainbreaw!

Committed to 8.8.x. Leaving RTBC for 8.7.x backport once the commit freeze is over (plus getting those two followups filed).

  • xjm committed 5adef51 on 8.7.x
    Issue #3041143 by tim.plunkett, cboyden, xjm, phenaproxima, rainbreaw:...
xjm’s picture

Backported to 8.7.x too. Just open for the followups; it can be marked fixed once they are filed.

tim.plunkett’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: -Needs followup

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.