Overview
Use a native HTML element first and add ARIA only where HTML cannot express the semantics. Full component patterns are in html-aria-patterns and accessibility.
Names and descriptions
An element’s accessible name comes from aria-labelledby, then aria-label, then native sources (<label>, alt, text content).
| Attribute | Use for |
|---|---|
aria-labelledby="id1 id2" | Naming from visible text already on the page. Preferred over aria-label. |
aria-label="text" | Naming when no visible text exists (icon buttons, landmarks). |
aria-describedby="id" | Supplementary hint or error text read after the name. |
aria-details="id" | Long or rich description, such as a figure’s explanation. |
aria-roledescription="text" | Custom role name that screen readers announce; use sparingly. |
<button aria-label="Close dialog"><svg aria-hidden="true">...</svg></button>
<section aria-labelledby="billing"><h2 id="billing">Billing address</h2> ... </section>
<input id="email" aria-invalid="true" aria-describedby="email-err">
<span id="email-err">Enter a valid email address.</span>State
Set state on the element the user interacts with, and update it from script.
| Attribute | Values | Use |
|---|---|---|
aria-expanded | true false | On the trigger of an accordion, menu, or disclosure, not on the panel. |
aria-pressed | true false mixed | Toggle buttons. |
aria-checked | true false mixed | Custom checkboxes, switches, radios; not for native inputs. |
aria-selected | true false | Tabs, listbox options, grid cells. |
aria-current | page step location date time true | The current item in a set, such as the active nav link. |
aria-invalid | true false | Field in error; pair with aria-describedby. |
aria-disabled | true | Stays focusable, unlike HTML disabled. |
aria-hidden | true | Removes the subtree from the accessibility tree (decorative icons). |
aria-busy | true false | Region is updating; hold announcements. |
Live regions
The live region must exist in the DOM before you change its text.
| Attribute | Values | Behavior |
|---|---|---|
aria-live | polite assertive off | Announce after the current speech / interrupt (use rarely). |
aria-atomic | true false | Read the whole region / only the changed node. |
aria-relevant | additions removals text all | Which changes announce (default additions text). |
role="status" / role="alert" | Implicit polite / assertive. |
<div role="status" id="status"></div> <!-- later: status.textContent = "Saved." -->Relationships and widgets
Use these in custom widgets only; native elements already expose the relationships.
| Attribute | Use |
|---|---|
aria-controls="id" | Trigger controls a panel or listbox. |
aria-owns="id1 id2" | Treat non-child elements as children. |
aria-activedescendant="id" | Focus stays on the container while the active item changes. |
aria-haspopup | menu listbox tree grid dialog or true. |
aria-posinset / aria-setsize | Position in a virtualized list (“3 of 50”). |
aria-level | Heading level for role="heading" (or tree item depth). |
aria-valuenow / -min / -max / -valuetext | Slider and progress values, with a readable text alternative. |
Gotchas
Avoid these; each can make an interface less accessible than no ARIA at all.
| Gotcha | Fix |
|---|---|
aria-label overrides visible text and native labels, so a button reading “Submit” labelled “Close” announces “Close”. | Keep the label equal to or starting with the visible text. |
aria-label is ignored on generic elements such as div and span. | Put it on interactive elements, landmarks, images, or give the element a role. |
aria-hidden="true" does not stop keyboard focus. | Hide with hidden or inert, never on focusable elements. |
| A live region created and filled in one step is often not announced. | Render it empty first, then update it. |
aria-required duplicates native required. | Use the native attribute. |
aria-disabled does not block clicks. | Also ignore the action in script. |