Standards · ARIA

Role Document structure

tabpanel

Marks the content panel paired with a tab. Each tabpanel is labelled by its tab via aria-labelledby; the tab references the panel via aria-controls. Show one panel at a time; hide the others with the hidden attribute.

When to use

On the container that holds the content for a tab. Pair each tabpanel with its tab via aria-labelledby (pointing at the tab’s id). The tab in turn points at the panel with aria-controls. Hide inactive panels with the hidden attribute — not just display: none via CSS — so the accessibility tree mirrors the visual state.

If the panel contains interactive content (a form, a list of links), you may make the panel focusable with tabindex="0". That way, after activating a tab, the next Tab keystroke lands inside the panel.

If the panel contains a single focusable region — say one large heading followed by content — tabindex="0" on the panel is enough. If the panel is full of focusable controls, the user will Tab to them directly and the tabindex is optional.

Common failures

  • Tabpanel hidden via CSS but not via the hidden attribute. Screen readers may still expose its content.
  • Tabpanel missing aria-labelledby — announced as just “tab panel” with no name.
  • Tab points at a tabpanel id via aria-controls but the panel does not point back via aria-labelledby. The pairing must be reciprocal.
  • All tabpanels live in the DOM with display: none switched dynamically, but no hidden attribute. Auditors flag the inconsistency.
  • Putting role="tabpanel" on a panel that is NOT paired with a role="tab" — orphan tabpanel.

Example

<div role="tablist" aria-label="Settings">
  <button role="tab" id="t-account" aria-selected="true"  aria-controls="p-account" tabindex="0">Account</button>
  <button role="tab" id="t-billing" aria-selected="false" aria-controls="p-billing" tabindex="-1">Billing</button>
</div>

<section role="tabpanel" id="p-account" aria-labelledby="t-account" tabindex="0">
  <h2>Account</h2>
  <p>…</p>
</section>
<section role="tabpanel" id="p-billing" aria-labelledby="t-billing" tabindex="0" hidden>
  <h2>Billing</h2>
  <p>…</p>
</section>