The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Prefer a native button | <button type="button">Save</button> | View examples |
| Declare a custom button role | <div role="button" tabindex="0">Save</div> | View examples |
| Name from visible text | <section aria-labelledby="billing-title">...</section> | View examples |
| Name an icon-only control | <button type="button" aria-label="Close">
<svg aria-hidden="true">...</svg>
</button> | View examples |
| Attach a description | <input id="password" aria-describedby="password-help"> | View examples |
| Expose disclosure state | <button aria-expanded="false" aria-controls="filters">
Filters
</button> | View examples |
| Expose toggle state | <button type="button" aria-pressed="false">Mute</button> | View examples |
| Mark the current item | <a href="/docs" aria-current="page">Docs</a> | View examples |
| Expose invalid state | <input
aria-invalid="true"
aria-describedby="email-error"> | View examples |
| Announce polite status | <div role="status" aria-live="polite"></div> | View examples |
| Announce urgent error | <div role="alert">Payment failed.</div> | View examples |
| Hide decorative graphics | <svg aria-hidden="true" focusable="false">...</svg> | View examples |
| Mark updates in progress | <section aria-busy="true" aria-live="polite">
...
</section> | View examples |
ARIA exposes semantics to accessibility APIs; it does not add keyboard behavior, focus management, validation, or visual styling. Prefer native HTML, preserve visible labels, add ARIA only where the platform lacks the required semantic, and test the computed accessibility tree together with keyboard and assistive-technology behavior.
Step by step
Detailed examples
Start with the element that already owns the behavior
Native controls provide semantics and interaction as one tested contract. Adding role=button to a div changes only the exposed role: authors must implement focus, Enter and Space activation, disabled behavior, and high-contrast states. ARIA must not contradict strong native semantics, and decorative descendants should not pollute a control name.
<button type="button" class="save-button">
<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24"><path d="M5 4h14v16H5z" /></svg>
Save changes
</button> Prefer visible text and native naming relationships
An accessible name identifies purpose; a description adds instructions or consequences. Use label, legend, caption, and button/link content first. aria-labelledby can combine visible text and usually takes precedence over aria-label; aria-describedby must reference existing descriptive content. Placeholder and title are weak fallbacks, not robust labels.
<label for="username">Username</label>
<input id="username" name="username" aria-describedby="username-help">
<p id="username-help">Use 4–24 letters, numbers, or underscores.</p>
<section aria-labelledby="billing-title">
<h2 id="billing-title">Billing details</h2>
</section> Synchronize ARIA state with visible and behavioral state
ARIA states report the interface; they do not change it. Update aria-expanded when content opens, aria-pressed when a toggle changes, and aria-current on exactly the current item. Keep control names stable across state changes—“Mute, pressed” is generally clearer than renaming the button between Mute and Unmute.
<button id="filters-button" type="button" aria-expanded="false" aria-controls="filters">Filters</button>
<section id="filters" hidden>...</section>
<script>
filtersButton.addEventListener('click', () => {
const open = filtersButton.getAttribute('aria-expanded') === 'true';
filtersButton.setAttribute('aria-expanded', String(!open));
filters.hidden = open;
});
</script> Move focus and error context deliberately
aria-invalid reports state but does not show an error, block submission, or focus the control. Render specific visible text, reference it with aria-describedby, and focus the first invalid field or an error summary according to the workflow. Do not announce errors on every keystroke unless immediate feedback is genuinely helpful.
<div role="alert" tabindex="-1" id="error-summary">
<h2>Correct one error</h2>
<a href="#email">Email: enter a valid address</a>
</div>
<label for="email">Email</label>
<input id="email" type="email" aria-invalid="true" aria-describedby="email-error">
<p id="email-error">Enter an address such as name@example.com.</p> Create live regions before inserting updates
A live region should exist before its content changes so assistive technology can observe the mutation. role=status is polite and implicit atomic behavior varies by role; role=alert is assertive and must be rare. Announce outcomes, not every implementation step, and avoid moving focus solely to force speech.
<button id="save" type="button">Save</button>
<div id="save-status" role="status" aria-live="polite"></div>
<script>
save.addEventListener('click', async () => {
save.disabled = true;
await Promise.resolve();
saveStatus.textContent = 'Changes saved.';
save.disabled = false;
});
</script> Local code tester
Inspect names, states, and live updates
Toggle a disclosure and status message while preserving native keyboard behavior.
Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



