a11y: Make scrollable sections keyboard-focusable
🚀 Feature Proposal
Scrollable sections in the UI should have a tabindex to allow keyboard-only users to navigate to them and scroll to access any static text. https://dequeuniversity.com/rules/axe/3.5/scrollable-region-focusable
Suggested requirements (based on the same feature we built into Workday Learning):
When there is overflow content, the container for that content requires
- a tabindex="0" to allow a keyboard user to focus the container and scroll the container using the arrow keys
- a role="group"
- an accessible name. In modals, one way to do this is to associate the modal title or header with the group using aria-labelledby.
When there is no overflow content, the containing div for the content doesn't need any of the above.
We achieved this in Learning modals by using a resize observer that conditionally adds the role=group, aria-labelledby and tabindex if it detects overflow in the scrollable area.
Motivation
As above and in https://dequeuniversity.com/rules/axe/3.5/scrollable-region-focusable
Example
This is probably most relevant for Modal components. https://canvas.workdaydesign.com/components/popups/modal/#body-content-overflow is an example. When you minimise the screen and use keyboard only to navigate to the modal, you can’t navigate to the scrolling section and see the overflow content.
Speaking to @NicholasBoll about this internally, he had some comments:
This seems like a common enough issue and there should be a scrollable container component that has these properties.
The deque article doesn't mention the necessity for semantic meaning of the tabstop. It is a group labelled by something, but it doesn't give any semantic meaning why that relationship is necessary. Is it okay if the
role=groupalways exists for possible overflow content that doesn't have specified keyboard navigation (for example, overflowed menus already have keyboard support)? Meaning should it be required to add a resize observer for all overflowed content to conditionally add arole=group?It makes sense for us to create a scrollable overflow component (or at least an
elemPropsHookto add functionality to any component) that is aware of amodelwith anidstate property that can accomplish what you have. If scrolling content needs keyboard accessibility everywhere, this functionality should be easy to integrate and the default everywhere that's applicable
There’s a design aspect to this too in that a focus outline needs to be accounted for when that container has overflow and has keyboard focus, and a visual treatment would help communicate visually there is actually overflow.
Low vision users have not perceived the browser generated scrollbars that are shown when a container has overflow in a recent study that included tables with overflow - so there’s an opportunity to improve this experience.
For compliance any visual treatment to communicate overflow would need to exceed the non-text contrast SC.
Here's my idea:
- Convert all models that have an
idto extend from a newidModeland make an elemPropsHook called "useOverflowContent" that takes theidand adds a resize observer that compares theoffsetHeightand thescrollHeightto see if the content is overflowed. It can always addstyle={{overflowY: 'scroll'}}. If overflowed, it adds:{ tabIndex={0} role="group" aria-labelledby={model.state.id} }
We'll have to watch out for overflow-limiting focus rings. This shouldn't be much of a problem usually unless we have scrollable areas inside of scrollable areas. We had this problem with Modal body being overflowed with buttons
bring up in #a11y to check priority on this -- enhancement not violation