Accessibility · ARIA & keyboard
ARIA & keyboard widgets
Guidelines applied to the product’s interactive components. Canonical references: MDN — Accessible web apps & widgets and MDN — Keyboard-navigable JavaScript widgets.
1. Semantics first, ARIA second
The golden ARIA rule: do not use ARIA when a native HTML element has the same semantics. A native <button> is always preferable to a <div role="button"> because it gets focus, Enter/Space activation, the role and the disabled state for free.
We only use ARIA to express what HTML cannot: dynamic states (aria-expanded, aria-pressed, aria-selected), relationships (aria-controls, aria-labelledby, aria-describedby) and live regions (aria-live).
2. tabindex — when and how
tabindex="0": makes a non-focusable element focusable in natural DOM order.tabindex="-1": focusable only via JavaScript (useful to move focus, e.g. to a freshly opened dialog or to<main>after the skip-link).- Never use a positive
tabindex: it breaks tab order and creates navigation barriers.
3. Key patterns for composite widgets
For widgets like tabs, menu, listbox, tree, combobox we follow the ARIA Authoring Practices keyboard patterns:
- Tabs: ←/→ move selection, Home/End to edges, Tab exits the widget.
- Menu / Listbox: ↑/↓ between items, Esc closes.
- Modal dialog: focus trapped while open, Esc closes, focus returned to the trigger.
- Toggle / Switch: Space toggles state (
aria-pressedoraria-checked).
These patterns are implemented in our Dialog, Switch, Tabs, Popover components (built on Radix UI, WAI-ARIA APG compliant).
4. Focus management
- Focus indicator always visible, contrast ≥ 3:1 with the background (WCAG 2.4.13).
- Focus never hidden behind sticky bars or tooltips (WCAG 2.4.11).
- When an action changes context (dialog open, navigation), we programmatically move focus to the new meaningful spot and return it to the source on close.
5. ARIA states and relationships we use
aria-current="page"on the active nav link.aria-pressedon toggles (Audio on/off, Reduce motion).aria-expanded+aria-controlsfor disclosure.aria-live="polite"for non-urgent messages,aria-live="assertive"for blocking errors.aria-describedbyto link form fields to error messages.role="status"for code-sandbox runtime state (Play/Stop, errors).
6. What we avoid
- Roleless clickables (
<div onClick>) — replaced by<button>or link. - Redundant ARIA: e.g.
role="button"on a native<button>. - Visual-only labels (icons without
aria-label) or text-only labels hidden under images. - Shortcuts that conflict with screen-reader keys (so global shortcuts are opt-in and documented).