vaadin / web-components

A set of high-quality standards based web components for enterprise web applications. Part of Vaadin 20+
https://vaadin.com/docs/latest/components
447 stars 83 forks source link

grid: sortable column headers don't convey their interactive nature #168

Open TetraLogicalHelpdesk opened 3 years ago

TetraLogicalHelpdesk commented 3 years ago

WCAG Level

Level A

Priority

High

Pages/screens/components affected

Description

Column headers that are sortable in a grid don't programmatically expose an interactive role. They are announced just as regular column headers, giving no indication that they are interactive.

Example of sortable column headers

User impact

When an assistive technology user reaches a column header, their screen reader will not announce the header as being interactive. If the header is already currently sorted, the sort order is announced ("ascending" or "descending"), which will provide a clue to users that they may be able to toggle it to change the sort order. But when a column header is currently not sorted, no special announcement/indication will be given to users. Users may miss that there is sort functionality at all.

Required solution

Interactive controls must be exposed as such programmatically. As sortable column headers act as buttons, they must have an appropriate role that conveys this.

Implementation guidance

Currently, sortable column headers use the same markup (once processed) as regular non-sortable headers, with the only exception being the aria-sort attribute.

<th ... tabindex="0" role="columnheader" ... aria-sort="none">...</th>

At a high level, you will also want to convey that the header contains a button - an interactive control which can be triggered by the user.

As a good example of a table with sortable column headers, see the WAI-ARIA Authoring Practices 1.1 Data Grid Examples, and specifically Example 2: Sortable Data Grid With Editable Cells. Note how the focusable element in this case is exposed with a role="button", but it is also still inside an appropriate <th> which has the aria-sort attribute (rather than the <th>'s native column header role being overridden).

Test procedure(s)

Use these steps to confirm that the solution has been correctly applied to issues identified within the test sample, and to test the rest of the website for instances of the same issue:

  1. Turn on your screen reader
  2. Navigate through the grid with sortable column headers.
  3. Verify that the sortable column headers are announced as buttons (and, if a sort order is set, that they still correctly announce the current sort order).
  4. Navigate through the data cells in the grid and verify that these column headers still correctly act as column headers, giving a name to the column they refer to.

Definition of done

Complete all of these tasks before closing this issue or indicating it is ready for retest:

Related standards

More information

Test data

Test date: March 2021 Website: vaadin.com/components, vaadin.com/docs-beta

web-padawan commented 3 years ago

I tried to set role="button" on the vaadin-grid-sorter element but it doesn't seem to work (tested in VoiceOver). This needs additional investigation and might require changing how grid behaves when focusing cells.

tomivirkki commented 3 years ago

Unlike the linked example, Vaadin grid doesn't automatically focus the focusable elements rendered inside the cells when navigating with arrow keys. The focus is by default always on the cell elements during arrow key navigation. You'll need to hit enter on a focused cell to have the first focusable element inside the cell to gain native focus, or hit space to click it without focusing.

Tried assigning role="button" to the header cell and it makes it announce as "Clickable - last name - button", which is better than just "last name", but doing so would also make it no longer announce the active sort direction at all. Is there any other way to indicate that there are focusable / clickable elements inside the focused cell?

tomivirkki commented 3 years ago

Solution proposal: Add aria-activedescendant="someId" to header cell elements (<th>) that include a slotted sorter. The "someId" should be the id (needs to be added) of the <slot> element within the header cell. Finally, the <slot> element should be assigned role="button".

This will make screen readers announce both the "button" role and the active sort-direction when the cell is focused. Confirmed this to work on JAWS and NVDA.

For VoiceOver users, the "button" role is also announced, but unfortunately, there's a regression that the sort direction is no longer announced at all. I noticed, however, that the referenced grid with a sortable column at https://www.w3.org/TR/wai-aria-practices-1.1/examples/grid/dataGrids.html#ex2_label doesn't announce the sort direction on VoiceOver either.

patrickhlauke commented 3 years ago

Tried assigning role="button" to the header cell and it makes it announce as "Clickable - last name - button", which is better than just "last name", but doing so would also make it no longer announce the active sort direction at all. Is there any other way to indicate that there are focusable / clickable elements inside the focused cell?

Unfortunately, not to my knowledge. the ARIA grid pattern (similar to other complex ARIA patterns) is fairly limited in what kinds of structures it wants/expects - I would hazard a guess that since it's not documented/usual to have a role="button" as a child of a role="row", and buttons don't have a sort order, browser/assistive technologies struggle to reconcile what should be announced/how this construct can be mapped into a sensible accessibility tree internally and exposed to users. Further attempts with aria-activedescendant may appear to work, but I'd say that if they're not documented in a solid ARIA pattern anywhere, they'll be unlikely to work consistently/at all in browser/AT combinations.

I just tried https://www.w3.org/TR/wai-aria-practices-1.1/examples/grid/dataGrids.html#ex2_label with VoiceOver/macOS and can confirm that, for whatever reason, VO seems buggy here and doesn't announce the sort order (though doing a quick check just with Chrome/NVDA, it's announced correctly there). Oddly, when I tried this very similar other example/test https://a11ysupport.io/tests/html/aria/aria-sort.html, that did announce "sort up"/"sort down" (though changes to sorting were not dynamically announced. I would try and deconstruct this particular test/grid to see if it's doing something that's not immediately apparent, but that gets it to work in VO for some reason.

tomivirkki commented 3 years ago

that did announce "sort up"/"sort down" (though changes to sorting were not dynamically announced

For me, this example didn't work either (on VO):

https://user-images.githubusercontent.com/1222264/115394805-dc479f80-a1eb-11eb-8e34-ea5c4b935897.mp4

patrickhlauke commented 3 years ago

I should have clarified ... in VO/macOS, it only announces the sort order when you navigate through the grid/table using Control + Option + cursor keys. and triggering the sort order button does not lead to any announcement...it only announces the sort order ("sort up", "sort down") when you leave and return to the cell. It's...minimal/broken, admittedly, support in VO seems to be fairly lacking.

https://user-images.githubusercontent.com/895831/115421839-3ea49e00-a1f4-11eb-99ed-acca9fcb6f7f.mp4

tomivirkki commented 3 years ago

in VO/macOS, it only announces the sort order when you navigate through the grid/table using Control + Option + cursor keys.

Just to clarify, this type of navigation makes the announcing work on VO with Vaadin grid also, as long as we add the role="button" to the sorter elements.

But the same as with Vaadin grid, the linked example doesn't announce the type "button" on JAWS/NVDA if the focus goes to the cell elements (needed to modify the example to make the cells focusable). When the buttons inside the cells receives native focus, "button" is announced properly on JAWS/NVDA.

One option is to make the Grid move the native focus to the sorter element as an exception, in case a sorter exists inside a cell being focused: (the styling in the video is temporary to showcase the location of the physical focus)

https://user-images.githubusercontent.com/1222264/115703582-f6f85080-a372-11eb-8504-26a51bbbaa7c.mp4

Some consequences of this approach:

If this approach is preferred, we need further discussion on