Standards · ARIA

Role Widget

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-controls linking 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>