So, you construct an information desk with lengthy rows, a lot of columns, and horizontal scroll on the container. It really works advantageous with a mouse and also you ship it.
However! When a keyboard consumer Tabs into the desk, its focus lands on a cell. Then they press the arrow keys to learn throughout the row however nothing occurs. They struggle Tab once more, however this time it jumps to the subsequent interactive ingredient completely exterior the desk. The remainder of the content material is there and display screen reader customers can navigate it simply advantageous, however there is no such thing as a option to scroll the container with out a mouse.
You’ll have by no means seen this as a result of you probably solely take a look at with a mouse. Your display screen reader customers by no means seen as a result of they navigate the accessibility tree, not the scroll container. The one particular person it traps is the sighted keyboard consumer and most groups by no means take a look at for that.
I didn’t both, till somebody filed a bug I couldn’t reproduce till I put my mouse away.
Keyboard focus follows tab order. The tab order then follows interactive components, like buttons, hyperlinks, inputs — mainly something the browser considers actionable. That’s the system the spec constructed for keyboard navigation.
Scroll containers are a very completely different system as a result of they’re format primitive. What I imply by that’s the browser handles overflowing content material in numerous methods any time you set overflow to auto, scroll, or hidden on a component. The browser’s job is to handle overflowing content material, not essentially navigate it. The spec by no means categorized overflow as interactive.
The hole between these two programs is the place keyboard customers fall by way of.
This isn’t a browser bug. Each main browser behaves identically right here as a result of they’re all following the identical spec and the scroll container and focus administration are each doing their jobs, though they weren’t designed to work collectively.
I briefly talked about a couple of overflow property values that have an effect on the way in which overflow is dealt with, however which values really create scroll containers? auto is the plain one, however the scroll containers have a behavior of showing the place you least anticipate them.
For instance, it’s value remembering that overflow is a shorthand. And it’s a little bit bizarre how the constituents work. Like, setting the overflow-x constituent to auto to deal with horizontal overflow implicitly units overflow-y to auto. So, a container you’re managing for horizontal scrolling can also be a vertical scroll container, and keyboard customers can’t scroll both axis with out a mouse.
/* This: */
.table-wrapper {
overflow-x: auto;
}
/* ...is the equal of this: */
.table-wrapper {
overflow-x: auto;
overflow-y: auto;
}
The identical factor occurs with overflow: hidden which most builders use it to clip content material or clear floats. It may not be apparent, however hidden nonetheless creates a scroll container. The content material is clipped, not gone (which may very well be an unintended type of “information loss”), and keyboard customers can nonetheless focus into it; they simply can’t scroll to any of it.
There are much less apparent triggers, too. Extra properties like rework, filter, perspective, will-change (referencing rework), include (set to paint), and content-visibility (set to auto) all create scroll containers. You’ll have added certainly one of these for a efficiency optimization or an animation and quietly created a keyboard entice on the identical time.
/* Added for a easy animation */
.panel {
rework: translateZ(0);
overflow: hidden; /* clipping the content material */
}
/* Consequence: .panel is now a scroll container
keyboard customers can focus into it however cannot scroll it */
One of many causes this bug ships so typically is that the scroll container wasn’t intentional and no person audited it as a result of no person knew it was there.
The Repair
The “repair” is including tabindex="0" to the scroll container within the markup:
That places the container within the tab order so keyboard customers can now Tab to it. And as soon as it has focus, the arrow keys scroll it. The browser handles the remainder.
However that's solely a part of the repair. Including tabindex with out an accessible title means a display screen reader encounters a focusable ingredient with nothing to announce. A plain div with tabindex="0" has no title, and the display screen reader says nothing helpful. It is advisable add an aria-label that describes what the container holds.
function="area" and aria-label work collectively. The function tells assistive know-how it is a landmark area. The label is what will get introduced when focus lands on it. With out the label, display screen readers announce”area” and nothing else. That tells the consumer nothing about what they’re about to scroll by way of.1
One other widespread drawback when working with scroll containers that have to help keyboard tabbing is utilizing seen and apparent focus styling. It’s widespread to see define: none or define: 0 as a design requirement. However then the container is selectable, however invisible. Keyboard customers will land on it with no clear indication. Focus types are certainly style-able, so we are able to nonetheless make then apparent and engaging if the design requires one thing customized:
.scroll-container:focus-visible {
define: 2px strong #005fcc;
outline-offset: 2px;
}
The define colour doesn’t have to match your model precisely, It simply wants a two-color mixture to make sure distinction with all elements, per WCAG’s strategies:
When tabindex="0" Isn’t the Reply
Making a scroll container focusable isn’t all the time the fitting name.
When you can’t write a transparent and helpful aria-label for the container, that’s a sign the content material would possibly want restructuring relatively than an interactive patch. A scroll container that wraps a single picture, an ornamental ingredient, or content material that’s already totally reachable by tab order doesn’t must be within the tab order itself. Placing it there provides noise with out including worth.
That stated, if a container holds a lot of focusable gadgets, fifty hyperlinks in a sidebar or an extended checklist of playing cards, a single Tab cease on the wrapper may very well be preferable to forcing keyboard customers by way of each merchandise individually. The trade-off shifts when the checklist is lengthy sufficient that tabbing by way of it turns into its personal burden. In these circumstances, tabindex="0" on the container is the fitting name even when the gadgets inside are already focusable.
The tougher name is when each merchandise inside already has impartial keyboard interplay, e.g., a listing of buttons, set of hyperlinks, or a bunch of type fields. All of those are already within the tab order so customers can attain every one with Tab. If the container is only a visible wrapper round already-accessible content material, making the wrapper focusable means customers need to Tab by way of one further cease to get to the issues they really wish to work together with.
The take a look at I run is an easy one. One thing like can a keyboard consumer attain each piece of significant content material contained in the container with out tabindex="0" on the wrapper? If sure, skip it. If no, add it.
When the fitting reply is skipping tabindex, restructuring the DOM is normally the higher path. Breaking apart lengthy content material, splitting it throughout sections, or utilizing a disclosure sample with progressive reveal typically solves the issue on the format degree with out creating accessibility compromise.
How one can Audit This
The keyboard-only walkthrough is the quickest take a look at. Unplug or disable your mouse, open the web page, and press Tab by way of each interactive ingredient and each scrollable container. When you attain a container with overflow content material and might’t scroll it with the arrow keys after tabbing to it, it wants fixing. On most pages, this could solely take about 5 minutes and the bugs are normally apparent the second you cease utilizing a mouse.
Be aware: One factor value being clear about is that this text is desktop-focused. Digital keyboards on iOS and Android work together with scroll containers in a different way, and contact navigation has its personal set of concerns. If cellular keyboard accessibility is a priority to your challenge, that warrants its personal investigation.
Chrome’s accessibility panel provides you a structural view. Open DevTools, go to the “Accessibility” tab, and examine a scroll container. If it exhibits no function and no accessible title, it’s invisible to assistive know-how as a navigable ingredient. That’s a fast option to affirm whether or not a container wants tabindex="0" and aria-label earlier than you contact the code.
Instruments like Deque’s Axe-core and WAVE can mechanically catch a few of these points. For Axe-core specification you should utilize the scrollable-region-focusable rule to flag scroll containers which have focusable content material however will not be themselves focusable. Working axe-core in your CI pipeline means this class of bug will get caught earlier than it reaches manufacturing relatively than after a consumer information a ticket.
// axe-core in a Jest take a look at
import axe from 'axe-core';
take a look at('scroll containers are keyboard accessible', async () => {
const outcomes = await axe.run(doc.physique, {
guidelines: { 'scrollable-region-focusable': { enabled: true } }
});
anticipate(outcomes.violations).toHaveLength(0);
});
One factor axe-core misses is containers which have overflow content material however no focusable youngsters. These gained’t set off the rule as a result of there’s nothing to Tab into. The keyboard walkthrough catches these circumstances however the automated software doesn't.
How I Method This Now
Listed below are the three questions I ask myself:
- Does the scroll container maintain content material that may’t in any other case be reached by keyboard**?** That’s stuff like an information desk, a code block, a chat log or a customized carousel — mainly something the place the one option to see all of the content material is to scroll. If the reply is sure, add
tabindex="0", anaria-label, and a visual:focus-visibletype. All three collectively. Not simply the attribute. - Can each piece of significant content material inside be reached by
**Tab**with out scrolling? If the container wraps a listing of hyperlinks or a bunch of buttons, the content material is already keyboard-accessible. That's, except there’s visually hidden content material resulting from overflow. If the container has overflow content material that’s not visually seen however is in tab order, customers nonetheless want a option to scroll to see it. Making the wrapper focusable provides tab stops with out including entry. Skip it. - Is the container the results of an unintentional
overflowset off fromrework,include, or comparable? When you added a property for causes unrelated to scrolling and it created a scroll container as a aspect impact, think about eradicating the property for those who can, or addtabindex="0"provided that there’s content material that genuinely wants it.
That’s the entire choice tree. The repair is easy as soon as you already know the container exists, however the arduous half is understanding it’s there. That’s what the keyboard walkthrough is for.
The Take a look at That Modifications How You Construct
The keyboard walkthrough takes not more than 5 minutes however most builders by no means run it as a result of they assume their customers use a mouse. Most of them are right, more often than not. However the sighted keyboard consumer is actual, they usually’re utilizing your product proper now, they usually’ve quietly realized which interfaces to keep away from as a result of they’re not definitely worth the frustration.
Utilizing tabindex="0" gained’t repair the whole lot. It gained’t repair a poorly structured DOM, a lacking accessible title, or a spotlight type that was stripped out in a worldwide CSS reset. However it closes the hole between what appears accessible and what really is, and it prices virtually nothing so as to add.
The factor I maintain coming again to is that this bug is invisible to the developer, invisible to the display screen reader consumer, and invisible to automated testing till you configure it particularly to look. The one option to discover it's to make use of the product the way in which the affected consumer does, which is the take a look at so put your mouse away.
