Skip to content

Accessibility

The Tabs component follows the WAI-ARIA Tabs pattern — a tablist whose tabs each reveal a panel — and meets WCAG 2.1 AA. The same strip carries two semantics: as a tab panel it switches content and uses the Tabs pattern; as a tab picker it is a single-select control built from a native radio group, so single-select, group labelling, and arrow-key roving all come from the browser with no ARIA. An accessible tabs strip stays reachable and operable from the keyboard, keeps assistive technology informed of which tab is selected and which panel it controls, and gives every element an accessible name — so the current view is clear whether the user is looking at it, listening to it, or tabbing through it.

  • All states (rested, hover, active, disabled) must maintain accessible contrast on every supported surface. Source: WCAG 1.4.3 Contrast (Minimum).
  • Selection must never rely on colour alone — reinforce it with the emphasised label weight and the active border, so it survives for colour-blind users. Source: WCAG 1.4.1 Use of Color.
  • Focus styles must stay visible, meet contrast requirements on every supported surface, and be clearly distinguishable from hover. Source: WCAG 2.4.7 Focus Visible.
  • Use semantic colour tokens (e.g. foreground/neutral/default) rather than fixed values, so the component adapts correctly across themes and surfaces.

Pick the pattern by what the component means, not how it looks — a tab panel that navigates content, or a tab picker that chooses one value.

A tab panel follows the WAI-ARIA Tabs pattern: a tablist of tab buttons, each revealing its own panel, with inactive panels kept in the DOM under the hidden attribute so the tab-to-panel relationship survives. The recipe uses auto-activation — moving to a tab with the arrow keys selects it and reveals its panel in one step — and keeps focus on the tab rather than shifting it into the panel.

A tab picker is a radio group, not a tablist — native radios sharing one name. It needs no ARIA and no JS: single-select, roving focus, and the group all come from the browser. Do not add role="tablist", role="tab", or role="radiogroup" — the native fieldset and radios already expose the group. The radio is visually hidden but stays focusable and present in the accessibility tree, so it announces as “radio button, n of m, group name”.

The per-element roles, names, and states are in the Labelling elements section below.

Both semantics are fully operable from the keyboard. Source: WCAG 2.1.1 Keyboard.

Tab panel (WAI-ARIA tablist, auto-activation):

Key Action
/ Move to and activate the previous / next tab
Home / End Move to and activate the first / last tab
Enter / Space Activate the focused tab
Tab Move focus out of the tablist to the next control

Tab picker (native radio group):

Key Action
/ Move to and select the previous option (native radio roving)
/ Move to and select the next option
Tab Move focus into the group (the checked option) / out of the group
Space Select the focused option

Interactive targets must be large enough for comfortable touch use, comfortably clearing the WCAG AA minimum. Source: WCAG 2.5.8 Target Size (Minimum).

Tab lands on the focused tab — a roving tabindex keeps only that one tab in the page tab sequence while the arrow keys rove between the others — then a further Tab leaves the tablist for the next control. The tab picker gets the same single-stop-then-rove behaviour for free from its native radio group. The keys that drive this are in the Keyboard interaction table above.

Overview content goes here.

  1. Focused tab

Source: WCAG 2.4.3 Focus Order.

Give every element the role, name, and state assistive technology needs.

Tab list

role="tablist" on the strip. Name it with aria-label (the recipe uses aria-label="Content tabs") or aria-labelledby, so it doesn’t announce as an unnamed group.

Tab

role="tab" on each <button>, with aria-selected kept in sync (true on the selected tab, false on the rest) and aria-controls pointing at the panel it reveals. Each tab takes its accessible name from its text content — keep labels short and self-describing.

Tab panel

role="tabpanel", named back to its tab with aria-labelledby, and hidden while inactive. Give it tabindex="0" when it holds no focusable content, so keyboard users can still reach the panel.

Tab picker group

The native <fieldset> exposes the radio group; its <legend> is the group’s accessible name — hide it with sr-only when the design shows no visible label. Each option’s name comes from its <label> text, and the checked radio is the selection, exposed through :checked rather than aria-selected.

Source: WCAG 4.1.2 Name, Role, Value.