A div with overflow auto and no tab stop is content a keyboard user cannot reach at all. Making it focusable is half the fix; an unnamed focusable region is announced as a group with no label. What to add, in what order, and why a rewrite is where this gets lost.
A div that scrolls and carries no tab stop is content a keyboard-only user cannot reach at all: there is nothing to focus, so there is nothing to press the arrow keys against, and every row past the fold is simply unavailable to them. Setting tabindex to zero fixes the reach and creates a second problem in the same move — a new stop in the tab order with no accessible name, which a screen reader announces as an unlabelled group in the middle of the page. The fix is two things, not one: make the container focusable, and give it a name.
Why is a scrolling div unreachable by keyboard?
Scrolling with a mouse needs no focus at all: the wheel or the trackpad scrolls whatever the pointer is over. Scrolling with a keyboard needs a focused element inside the scroll box, because arrow keys and page keys move the scroll position of whatever currently has focus. A container with overflow set to auto, no focusable children and no tab stop of its own has no entry point, so the content below the visible edge cannot be read by anyone navigating with a keyboard. That is a failure of WCAG success criterion 2.1.1, which is Level A.
All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes, except where the underlying function requires input that depends on the path of the user's movement and not just the endpoints.
The case that makes this concrete is a data table inside a scroll box. A wide table needs its own horizontal scroll container, because without one the whole page scrolls sideways and the site's chrome starts sliding off the screen. The container solves that and quietly takes the rows with it: everything below the box's visible height is now inside something a keyboard cannot enter.
What does tabindex of zero add, and what does it cost?
Setting tabindex to zero on the scroll container puts it in the natural tab order, which gives a keyboard user something to focus and therefore something to scroll. That is the whole benefit and it is a large one. The cost arrives immediately: the page now has a tab stop that is not a control, does not do anything when activated, and has no name. A screen reader user tabbing through the page lands on it and is told there is a group here, with no indication of what the group holds or why they are in it.
The link measured 297 by 44 pixels and reported a pass. Tapping its centre hit the paragraph behind it. An inline box that wraps is not one rectangle, and the pseudo-element meant to enlarge it was positioned against the same broken geometry.
Market4 turns one release note into a changelog page, a blog post, a mail-out and a week of social posts — and then tells you which of them brought anyone back.
No card to start. Cancel from the settings screen, not from an email.
Why does the region need a name?
A focusable container should be announced as a region, and the ARIA specification is explicit that a region carries a label. Declaring the role without a name leaves both problems in place: the element is now important enough to be a tab stop and a landmark, and it still tells the person who reached it nothing. The rule is short enough to quote in full.
Authors MUST give each element with role region a brief label that describes the purpose of the content in the region.
The name should be the string that already describes the content, not a new one written for assistive technology. A table has a caption; that caption is the table's accessible name, and it is the right name for the region around it. Reusing it keeps one string in one place, so renaming the table renames the region, and there is no second label to fall out of step with the first. A separate aria-label invented for the container is a label that will be wrong within a year.
A new tab stop owes the keyboard a visible focus state
Any element that can receive keyboard focus needs a visible focus indicator, and a scroll container is not exempt because it is not a button. Without one, a keyboard user tabs, the focus ring disappears, and the next arrow key scrolls something they cannot see they are inside. Give the container the same focus treatment every other focusable element in the interface gets — an outline with an offset, drawn on keyboard focus rather than on click.
What to add, and what each part fixes
Five additions, each answering a different way the container fails.
What you add
What it fixes
What happens without it
A bounded height on the container
There is something to scroll in the first place
The box is content height and nothing ever scrolls
tabindex of zero on the container
The keyboard can reach the scrollable content
Rows past the visible edge are unreachable
A region role on the container
The stop is announced as a landmark
The stop is an anonymous generic element
An accessible name matching the visible caption
The region is announced by what it contains
A screen reader says group, and nothing else
A visible focus indicator
A keyboard user can see where they are
Focus lands somewhere invisible and arrow keys move something unseen
Where this gets lost
Component rewrites are where a focusable named region disappears. Nothing fails a build over a missing tabindex, no unit test notices an absent aria-label, and the rendered result looks pixel-identical to the version that had both. The component that shipped with them and the component that replaced it are indistinguishable in a screenshot review, and every screen that moved across the migration lost the same thing on the same day. The habit worth building is to treat the tab stop and the name as part of the component's contract, written down beside its props, rather than as styling that survived by luck.
There is a side benefit worth noticing, though it is not the reason to do any of this. A named region is addressable by that name in tests, so an end-to-end suite can ask for the region by its caption instead of reaching for a class name or a nth-child selector. Accessibility names make durable test handles because they are the names people use, and they change only when the visible label changes.
Does a scrolling div need a tabindex?
Yes, when the div is the thing that scrolls and nothing inside it is focusable. Keyboard scrolling works by moving the scroll position of the focused element, so a scroll box with no focusable entry point cannot be scrolled from the keyboard at all, and the content below its visible edge is unreachable. Setting tabindex to zero puts it in the natural tab order and gives the keyboard somewhere to stand.
Why does my screen reader announce a group with no label?
Because the element it landed on is focusable and has no accessible name. Adding a tab stop to a container without also naming it produces exactly this: the user is told there is something here and given nothing to identify it with. Add a role of region and a name drawn from whatever visibly describes the content, and the announcement becomes the caption the sighted reader is looking at.
What name should a scrollable region get?
The one already on screen. If the region wraps a table, use the table's caption. If it wraps a card, use the card's heading. Referencing the existing visible text rather than writing a new label keeps the two in step, so a rename in one place is a rename everywhere, and a screen reader user hears the same words a sighted user reads. Invented labels drift; borrowed ones cannot.
Does adding tabindex cause problems of its own?
It can, in two ways worth watching. A new tab stop needs a visible focus style, or keyboard users lose track of where they are. And tabindex should not be added to elements that are already focusable, or to static content that does not scroll, because each unnecessary stop lengthens the path through the page for the people who navigate it one key at a time.
A table header declared sticky sat still while the rows scrolled past it, with no error anywhere. Two separate mistakes: a wrapper that could not overflow, and the sticky rule on the wrong element. Here is what each one does and how to tell them apart.
The accessibility floor is written by redefining tokens inside media queries. That only keeps working if every Tailwind utility emits a var() reference rather than a compiled copy.