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.
Two principles
- 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. - 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.
| Component | ARIA | What it gets |
|---|---|---|
| Site header | role='banner' | Implicit <header> at top-level |
| Site nav | role='navigation' | aria-label='Main navigation' |
| Footer nav | role='navigation' | aria-label='Footer' |
| Main | role='main' | Implicit <main>, id='main-content' |
| Search trigger | role='button' | aria-label='Open search' |
| Active link | aria-current='page' | When on the linked route |
| Icon button | role='button' | aria-label required |
| Dialog | role='dialog' | aria-modal='true', aria-label required |
| Sheet | role='dialog' | aria-modal='true', aria-label required |
| Alert (info) | role='status' | Live region, polite |
| Alert (destructive) | role='alert' | Live region, assertive |
| Field error | role='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
- Every interactive element has an accessible name. Either visible text, an
aria-label, or anaria-labelledby. - Every form field has a
<label>. ThehtmlFor/idassociation is required;placeholder-as-label is rejected in review. - Decorative icons are
aria-hidden="true". Informational icons get anaria-label. - Hidden content is hidden by
display: noneorvisibility: hidden, not byaria-hiddenalone (a sighted keyboard user can still tab toaria-hiddencontent). - Live regions are set on the wrapper, not on the message that appears. The wrapper's role is permanent; only the content changes.