Search topics
Tabs that only work with a mouse
Product tabs are list items with click handlers, so they take no keyboard focus and say nothing about which panel is shown.
<li class="tab active" onclick="show('description')"> The problem
Keyboard users can't open any tab but the first, and screen reader users aren't told that the tabs switch content or which one is shown.
A product page for a jacket has three tabs under the photo: Description, Sizes and Reviews (12). Click Sizes and the size table replaces the description. Press Tab instead and focus jumps over all three tabs, straight to the Care instructions link inside the description. There is no key that opens Sizes or Reviews. A screen reader user reading the page hears a list of three words, then the description, and nothing to suggest that two more panels are waiting behind the other words.
Each tab is an li with a click handler. A list item is not a control: it takes no focus, answers no keys, and has no role a screen reader can announce. The click handler only works for a pointer, so the panels behind it fail WCAG 2.1.1 Keyboard. The selected tab is shown by an active class, which only the stylesheet reads, so the tab's role and state fail 4.1.2 Name, Role, Value: nothing in the code says that these words are tabs, that they control the panels below, or which one is selected.
Product pages, account settings and pricing tables often put the details people came for behind the second or third tab. When the tabs work only with a mouse, the people they shut out lose the content, not just a shortcut to it.
2 of 10 groups affected
- No vision (affected)
- Low vision (not affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (affected)
- Reach, strength (not affected)
- Cognitive (not affected)
- Seizures (not affected)
- Limited manipulation Keyboard and switch users can't reach Sizes or Reviews at all, so whatever those panels hold is out of reach. Voice control users may not be able to say "click Sizes", because nothing exposes the item as a control.
- Without vision A screen reader reads the tabs as a list of words, with no sign that they switch content or which one is shown, so the person may never learn the other panels exist.
A probe that compares click handlers with the Tab order can find list items that react to clicks but never take focus. It can't tell whether the selected state is exposed, whether that state moves when another tab is chosen, or whether the arrow keys work. AI code generators often add role="tab" and aria-selected to the same list items and stop there. That is not a fix: a screen reader now says "tab", but the items still can't be reached with Tab or opened with a key, so the person is told about a control they can't use. Another common patch renders only the shown panel, which leaves aria-controls on the other tabs pointing at ids that don't exist. Pressing Tab, the arrow keys and Enter on every tab, then listening to each one, is the only way to confirm the pattern works.
Try it
Open the Sizes tab with the keyboard.
Press Tab, then try the arrow keys, Home and End. The panel shows what a screen reader says. Does it say which tab is shown?
Broken Can you fix this?
- Description
- Sizes
- Reviews (12)
Waxed cotton, lined with wool. Care instructions
Screen reader says:
(Press Tab)A recreated demo. The broken version is intentionally inaccessible.
The fix
Build the tabs pattern on buttons: one Tab stop, the arrow keys, and aria-selected on the shown tab.
- 1Give the tab list one Tab stop, and move between tabs with the arrow keys, Home and End.
- 2Mark the shown tab with
aria-selected="true", and draw its look from that attribute. - 3Hide the other panels with
hidden, and keep them in the page.
<ul class="tabs"> <li class="tab active" onclick="show('description')">Description</li> <li class="tab" onclick="show('sizes')">Sizes</li> <li class="tab" onclick="show('reviews')">Reviews (12)</li></ul><div class="panel" id="description"> <p>Waxed cotton, lined with wool. <a href="/care">Care instructions</a></p></div><div class="panel" id="sizes" style="display: none">…</div><div class="panel" id="reviews" style="display: none">…</div><div role="tablist" aria-label="Product details"> <button type="button" role="tab" id="tab-description" aria-selected="true" aria-controls="panel-description">Description</button> <button type="button" role="tab" id="tab-sizes" aria-selected="false" aria-controls="panel-sizes" tabindex="-1">Sizes</button> <button type="button" role="tab" id="tab-reviews" aria-selected="false" aria-controls="panel-reviews" tabindex="-1">Reviews (12)</button></div><div role="tabpanel" id="panel-description" aria-labelledby="tab-description" tabindex="0"> <p>Waxed cotton, lined with wool. <a href="/care">Care instructions</a></p></div><div role="tabpanel" id="panel-sizes" aria-labelledby="tab-sizes" tabindex="0" hidden>…</div><div role="tabpanel" id="panel-reviews" aria-labelledby="tab-reviews" tabindex="0" hidden>…</div>Why this fix: Style from the state, not a class
The list items are never in the Tab order, so the keyboard can't open Sizes or Reviews. The active class shows the selected tab to the eye only.
The tab list takes one Tab stop, on the selected tab, and the arrow keys move between tabs and show each one's panel as they go; Home and End jump to the ends. A screen reader announces something like "Sizes, tab, selected, 2 of 3". Every panel stays in the page, hidden, so aria-controls always points at a real element and the links in hidden panels take no focus. The panels have tabindex="0" because they start with text, so the next Tab after the tab list lands on the panel itself. The selected look is drawn from aria-selected, so it can't show one tab while the code marks another.
A tested tabs component does all of this for you, and is usually safer than a hand-written script. Check that the one you use gives the tab list one Tab stop, moves with the arrow keys, sets aria-selected, and hides the other panels; some libraries style a row of buttons as tabs and stop there. The pattern still leaves several choices to you.
- Automatic or manual activation: the code above shows each panel as the arrow keys reach its tab. If a panel loads slowly, show it only on Enter or Space, and let the arrow keys move focus without selecting.
- Vertical tabs: add
aria-orientation="vertical"to the tab list and use the Up and Down arrows instead. - Panels that render on demand: frameworks often render only the selected panel. Render every panel and hide the others with
hidden, or setaria-controlsonly on the selected tab, so it never points at nothing and an audit has nothing to report. - Narrow screens: if the tabs turn into an accordion on a phone, switch the roles with the layout. A row of
role="tab"elements stacked vertically is still announced as tabs. - Panel focus: the panels here start with text, so they take
tabindex="0". A panel that starts with a link or a field needs no tabindex, since the next Tab lands on that control.
Tabs that each load a different page are not a tab widget at all. They are navigation: use links in a nav, and mark the current one with aria-current="page". Keep the selected tab visible in forced colors with a border or weight, not a background colour alone.
Fixing this with an AI coding assistant? Get this guide as Markdown
Verify the fix
5 checks, no mouse.
Test with: Keyboard, Screen reader, Zoom, Forced colors
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared tabs component changes.
Limits
This is a starting pattern, not a guarantee for every application. The script is a minimal sketch: it handles one tab list per page, and has no deep links or remembered selection. Screen readers word tabs and their position differently. Test the complete journey with your target assistive technology.
This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.
Keep this fixed as your code changes.
Turn guides like this into rules your coding agents and CI follow, then have the result retested with a keyboard and screen readers.