[RFC] Improve accessibility via support for composite widgets
See original GitHub issueThis is a continuation of the discussion started by @mbrookes in #15334 and #15421. I have not yet made any attempts at an implementation, but I wanted to go ahead and document my thoughts about the problem space.
What is a composite widget?
WAI-ARIA describes a composite widget as having the following characteristics:
- Always contains multiple focusable elements.
- Only one of the focusable elements contained by the widget is included in the page tab sequence.
- Requires the author to provide code that manages focus movement inside it.
What are examples of composite widgets?
WAI-ARIA describes the following design patterns and widgets as composite widgets:
- Combo Box: https://www.w3.org/TR/wai-aria-practices/#combobox
- Grids: https://www.w3.org/TR/wai-aria-practices/#grid
- Listbox: https://www.w3.org/TR/wai-aria-practices/#Listbox
- Menus: https://www.w3.org/TR/wai-aria-practices/#menu
- Radio Group: https://www.w3.org/TR/wai-aria-practices/#radiobutton
- Tabs: https://www.w3.org/TR/wai-aria-practices/#tabpanel
- Toolbar: https://www.w3.org/TR/wai-aria-practices/#toolbar
- Tree View: https://www.w3.org/TR/wai-aria-practices/#TreeView
- Treegrid: https://www.w3.org/TR/wai-aria-practices/#treegrid
What is the accessibility benefit of composite widgets?
Composite widgets allow keyboard users to navigate quickly between different interactive portions of the page. <kbd>tab</kbd> and <kbd>shift</kbd>+<kbd>tab</kbd> can be used to skip between different composite widgets and to any focusable elements that are not part of a composite, and then the arrow keys can be used to navigate within a composite widget.
What is the current state of support for composite widgets in Material-UI?
The following widgets are already treated as composites in their Material-UI counterparts:
- Combo Box: https://material-ui.com/demos/autocomplete/
- Listbox: https://material-ui.com/demos/selects/
- Menus: https://material-ui.com/demos/menus/
- Radio Group: https://material-ui.com/demos/selection-controls/#radio-buttons
The following widgets have a Material-UI counterpart that is not treated as a composite widget (i.e. <kbd>tab</kbd> navigates within these widgets rather than skipping to the next focusable element outside the widget):
- Grids: https://material-ui.com/demos/lists/
- Tabs: https://material-ui.com/demos/tabs/
- Toolbar: https://material-ui.com/demos/app-bar/
The Tree View and Treegrid patterns do not currently have any direct Material-UI counterpart, however nested Lists can be used to create tree widgets. The Grid pattern can also be implemented in Material-UI using components other than Lists, for instance a grouping of Cards could be considered a WAI-ARIA Grid. If each Card has multiple focusable elements, then each Card in a group of Cards would represent a row in the grid.
What should be done to improve support for composite widgets in Material-UI?
Adding composite widget support for Lists
In the pull requests by @mbrookes that preceded this issue, the main idea was to move the keyboard focus navigation from MenuList down into List so that the focus logic could be used more broadly. While I think something along these lines is possible, there are a number of challenges to address:
- In order for a composite widget to add benefit, it is not enough to simply add arrow key navigation. It is also necessary to remove all focusable elements within the composite widget from the tab sequence by specifying a
tabindex
of -1, so that <kbd>tab</kbd> and <kbd>shift</kbd>+<kbd>tab</kbd> can be used reliably to skip out of the composite widget. - The current MenuList focus navigation assumes that the focusable elements are all direct children of the list. Lists, however, can have a much more varied structure including secondary actions and nested lists. Any solution for treating Lists as composite widgets should handle secondary actions, and ideally support nested lists as well.
- The accessibility benefits of composite widgets are most important for long lists, so virtualized lists should be supported as well (though potentially with a more complex DX than for regular lists). It may not be practical to try to support text navigation for virtualized lists, but the solution should have an approach for handling <kbd>home</kbd> and <kbd>end</kbd> within a virtualized list. I wouldn’t expect the arrow keys to pose any special difficulties with virtualized lists.
- For Lists, it will be important to support a roving tabindex: https://www.w3.org/TR/wai-aria-practices/#kbd_roving_tabindex. This concept was recently removed from MenuList as part of improving performance. In MenuList it added very little value since <kbd>tab</kbd> generally closes the Menu, so there is no reason to retain where the focus was. For composite widgets that persist on the page, the roving tabindex is much more important. If a user tabs out of the composite widget and then uses <kbd>shift</kbd>+<kbd>tab</kbd>, they should return to the same point in the composite widget. In order to avoid the performance issues that the roving tabindex caused within MenuList, it is important to manage this without leveraging state in the parent. Focus navigation should not cause re-rendering of the List; at most only the two list items involved in the focus change should be re-rendered. This means that changing of the tabindex (such that the current focus element in the composite has a tabindex of zero) should be done via the DOM property rather than via the attribute.
Adding composite widget support for other components
I think it would be worthwhile to consider other scenarios (e.g. group of Cards, AppBar, Tabs) at the same time to explore which implementation aspects might make sense to be shared and to work towards a more general solution for composite widgets, but I’m not sure if a general solution will actually make sense in the end.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:9
- Comments:10 (10 by maintainers)
There are quite a few complex possible scenarios. The most complex involve editable content. The ARIA documentation provides guidance for toggling between grid navigation vs. editing here, but I won’t even try to handle that in the initial implementation. Whether we can consider switching to opt-out in the future depends on how mature this is at that point and how rare the scenarios are that aren’t handled well.
Is there something left to do on this issue?