The essentials

Quick reference

One focused task per row. Jump to the related section for complete, working examples.

UseSyntaxExamples
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

01

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.

Native action with a decorative icon
<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>
Back to quick reference ↑
02

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.

Visible name with separate help
<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>
Back to quick reference ↑
03

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.

Disclosure with synchronized state
<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>
Back to quick reference ↑
04

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.

Field error with a visible summary
<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>
Back to quick reference ↑
05

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.

Polite save confirmation
<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>
Back to quick reference ↑

Local code tester

Inspect names, states, and live updates

Toggle a disclosure and status message while preserving native keyboard behavior.

Runs in your browser
Preview

Sources and further reading

References

Authoritative documentation used to verify and expand this cheat sheet.

  1. World Wide Web ConsortiumAccessible Rich Internet Applications (WAI-ARIA) 1.2w3.org
  2. World Wide Web ConsortiumARIA in HTMLw3.org
  3. W3C Web Accessibility InitiativeProviding Accessible Names and Descriptionsw3.org
  4. W3C Web Accessibility InitiativeARIA Authoring Practices Guidew3.org

Help us improve

Found a typo or missing example?

Tell us what would make this cheat sheet clearer, more complete, or more useful.

Share feedback