When to use this pattern
Tabs suit content of equal weight that people do not need to see at the same time, such as product details, specifications and delivery. If visitors need the content in sequence or want to compare it, a normal page with subheadings is often better.
Tabs are not navigation: if an item takes you to another page, it is a link in a <nav>, not a tab.
Roles, states and properties
- A container with
role="tablist"and a name viaaria-labeloraria-labelledby. - Each tab is a
<button role="tab">witharia-selectedandaria-controls. - Only the active tab has
tabindex="0"; all others havetabindex="-1"(roving tabindex). - Each panel has
role="tabpanel",aria-labelledbypointing to its tab, andtabindex="0"if it contains nothing focusable. - Inactive panels are hidden with
hidden.
Keyboard interaction
| Key | Effect |
|---|---|
| Tab | Moves into the tab list onto the active tab; pressed again, into the panel. |
| Left / Right arrow | Moves to the previous or next tab and shows it (wraps around at the ends). |
| Home / End | Moves to the first or last tab. |
Code
<div role="tablist" aria-label="Product info">
<button role="tab" id="t1" aria-selected="true"
aria-controls="p1">Details</button>
<button role="tab" id="t2" aria-selected="false"
aria-controls="p2" tabindex="-1">Delivery</button>
</div>
<div role="tabpanel" id="p1" aria-labelledby="t1"
tabindex="0">…</div>
<div role="tabpanel" id="p2" aria-labelledby="t2"
tabindex="0" hidden>…</div>
// Arrow keys: tabs[i].focus(), then select(i)
list.addEventListener('keydown', e => {
const n = { ArrowRight: i + 1, ArrowLeft: i - 1,
Home: 0, End: tabs.length - 1 }[e.key];
if (n === undefined) return;
e.preventDefault();
select((n + tabs.length) % tabs.length);
});Common mistakes
- Every tab is its own tab stop, so moving past the widget means tabbing through all of them.
- Tab roles without arrow-key support: screen readers announce tabs that then do not behave like tabs.
- Active tab marked by colour only, without
aria-selectedand without a second visual cue. - Links that change the page marked up as tabs.
Related WCAG criteria
Sources
Are your components built like this?
The free check finds many issues automatically. In the audit a person tests every component with a keyboard and a screen reader.