Skip to content

AccessibilityScreen reader

Screen reader

The system uses the smallest amount of ARIA that does the job. Native HTML first; ARIA only when HTML isn't enough.

Native HTML first12 components with explicit ARIALive regions for status and errors

Two principles

  1. Native first. A <button> is a button. A <nav> is a nav. A <dialog> is a dialog. ARIA is added only when the native element isn't enough.
  2. The smallest amount that does the job. A role="button" on a <div> is a code smell. Use a <button>. The same applies to menus, tabs, tooltips, and every other "ARIA role" — if there's a native element, use it.

Components and their ARIA

The twelve components that use explicit ARIA. Everything else relies on native semantics.

ComponentARIAWhat it gets
Site headerrole='banner'Implicit <header> at top-level
Site navrole='navigation'aria-label='Main navigation'
Footer navrole='navigation'aria-label='Footer'
Mainrole='main'Implicit <main>, id='main-content'
Search triggerrole='button'aria-label='Open search'
Active linkaria-current='page'When on the linked route
Icon buttonrole='button'aria-label required
Dialogrole='dialog'aria-modal='true', aria-label required
Sheetrole='dialog'aria-modal='true', aria-label required
Alert (info)role='status'Live region, polite
Alert (destructive)role='alert'Live region, assertive
Field errorrole='alert'Inside aria-describedby of the input

Live regions

Two components use live regions. The choice betweenrole="status" (polite) and role="alert"(assertive) is the difference between a confirmation and a warning.

  • Info and success alerts use role="status": the screen reader announces the change when convenient, not immediately.
  • Destructive alerts and form errors use role="alert": the screen reader announces the change immediately, interrupting other speech.

Rules of thumb

  1. Every interactive element has an accessible name. Either visible text, an aria-label, or an aria-labelledby.
  2. Every form field has a <label>. The htmlFor/id association is required; placeholder-as-label is rejected in review.
  3. Decorative icons are aria-hidden="true". Informational icons get an aria-label.
  4. Hidden content is hidden by display: none or visibility: hidden, not by aria-hidden alone (a sighted keyboard user can still tab to aria-hidden content).
  5. Live regions are set on the wrapper, not on the message that appears. The wrapper's role is permanent; only the content changes.

What not to do