tab
Marks an element as one tab in a tabbed interface. A tab MUST live inside a tablist, and SHOULD reference its associated tabpanel via aria-controls. There is no native HTML tab element — tabs are an ARIA-only pattern.
When to use
For one tab in a horizontally- or vertically-arranged tab strip. A tab MUST be a direct child of a role="tablist" element (or owned via aria-owns). Each tab MUST point to its associated panel with aria-controls="<tabpanel-id>". The reciprocal aria-labelledby lives on the panel, pointing back at the tab.
Tabs are an application pattern. If your “tabs” are really just navigation between separate pages, use <nav> with <a> links instead — the URL change makes the tab pattern wrong.
The tab itself is usually a <button>. The button semantics combine cleanly with role="tab", and the focus + activation handling comes for free.
Keyboard + focus contract
Per the APG tabs pattern:
- Tab moves into the tablist, onto the active tab. Tab moves OUT to the next focusable element (typically into the tabpanel itself if it has
tabindex="0"). - Left/Right arrows (or Up/Down for vertical tablists) move focus between tabs.
- Home / End jump to the first / last tab.
- Activation can be automatic (focus = selection) or manual (Space/Enter to select). Choose manual when activating a tab triggers expensive work.
Use a roving tabindex: only the selected tab has tabindex="0".
Common failures
- All tabs with
tabindex="0"— Tab cycles through every tab rather than the panel. - Missing
aria-controlslinking tab to tabpanel. aria-selected="true"on more than one tab.- Tabs implemented with
<a href="#section">— the URL fragment changes, the back button now navigates between tabs, and the pattern breaks down. - No focus management on activation: clicking a tab updates the panel but the next Tab keystroke goes back to the page header rather than into the panel.
Example
<div role="tablist" aria-label="Account settings">
<button role="tab" id="t-profile" aria-selected="true" aria-controls="p-profile" tabindex="0">Profile</button>
<button role="tab" id="t-billing" aria-selected="false" aria-controls="p-billing" tabindex="-1">Billing</button>
<button role="tab" id="t-security" aria-selected="false" aria-controls="p-security" tabindex="-1">Security</button>
</div>
<section role="tabpanel" id="p-profile" aria-labelledby="t-profile" tabindex="0">…</section>
<section role="tabpanel" id="p-billing" aria-labelledby="t-billing" hidden>…</section>
<section role="tabpanel" id="p-security" aria-labelledby="t-security" hidden>…</section>