Go Back

ARIA (Accessible Rich Internet Applications)

What is ARIA?

ARIA (Accessible Rich Internet Applications) is a set of attributes you add to HTML so assistive technologies (e.g. screen readers) can understand roles, states, and relationships that native HTML doesn’t provide. Use ARIA when you build custom widgets (tabs, dialogs, menus) or when you need to expose dynamic state (expanded, selected, invalid). Prefer semantic HTML when it already does the job.

Use it when: you have custom interactive components (e.g. a div that acts as a button, a custom dropdown, a modal) and need to expose their role, name, state, and keyboard behaviour to assistive tech. Don’t use ARIA to fix bad structure; fix the HTML first.

Copy/paste checklist (when using ARIA)

  • Prefer HTML – Use <button>, <a>, <input>, <label>, <main>, <nav>, etc. before adding ARIA. Only use ARIA when there’s no suitable HTML element.
  • Role – role="button", role="dialog", role="tablist", etc. so the control is announced correctly.
  • Name – aria-label or aria-labelledby so the element has an accessible name.
  • State – aria-expanded, aria-selected, aria-checked, aria-invalid, etc. so current state is announced.
  • Relationships – aria-controls, aria-describedby, aria-owns where they help (e.g. button controls panel).
  • Keyboard – Implement keyboard navigation and focus management that match the role (e.g. dialog: trap focus, Escape closes).
  • Don’t override semantics – Don’t add role="button" to a <button>; don’t use ARIA where HTML already exposes the same thing.

Why ARIA matters

  • Custom UI (divs and spans with JavaScript) has no meaning for assistive tech without ARIA (or without replacing it with semantic HTML).
  • WCAG 4.1.2 (Name, Role, Value) requires that widgets have an accessible name, role, and state; ARIA is how you provide them for custom components.
  • Screen reader users need correct role and state announcements to use tabs, dialogs, and menus.
  • ARIA doesn’t change behaviour; you must still implement keyboard and focus management yourself.

What good ARIA use includes

Checklist

  • Only when needed – Use semantic HTML first; ARIA only for custom widgets or when HTML can’t convey the same.
  • Correct role – Use a role that matches the behaviour (e.g. role="dialog" for a modal, not role="alert").
  • Name – Every interactive element has an accessible name (visible text, aria-label, or aria-labelledby).
  • State in sync – aria-expanded, aria-selected, etc. match the actual UI state; update them when state changes.
  • Keyboard and focusKeyboard navigation and focus management follow the pattern for that role (see ARIA Authoring Practices Guide).
  • Tested – Test with a screen reader so role, name, and state are announced correctly.

Common patterns

  • Custom button: role="button", tabindex="0", aria-label or visible text; Enter and Space activate.
  • Dialog: role="dialog", aria-modal="true", aria-labelledby (and optionally aria-describedby); focus trap; Escape closes.
  • Tabs: role="tablist", role="tab", role="tabpanel"; aria-selected, aria-controls; arrow keys and Tab per pattern.
  • Live updates: aria-live="polite" or "assertive" for regions that update (e.g. errors, success messages).

Examples

Example (the realistic one)

Custom “Save” control: It’s a div with a click handler. You add role="button", tabindex="0", aria-label="Save changes", and key handler for Enter and Space. You don’t use a <button> because of legacy or design constraints. Modal: role="dialog", aria-modal="true", aria-labelledby="dialog-title". On open, focus moves to the dialog (or first focusable); Tab is trapped; Escape closes and returns focus. You test with NVDA: “Save changes, button” and “Dialog, Save your work?” are announced; keyboard works. See focus management and keyboard navigation.

Common pitfalls

  • ARIA on semantic HTML: role="button" on a <button>. → Do this instead: Omit redundant ARIA; native HTML already has the role.
  • Missing keyboard: ARIA role without matching keyboard behaviour. → Do this instead: Implement the keyboard pattern for that role (see ARIA APG).
  • State not updated: aria-expanded stays true after closing. → Do this instead: Update ARIA state whenever the UI state changes.
  • No accessible name: role="dialog" but nothing says what the dialog is. → Do this instead: Use aria-labelledby (and optionally aria-describedby) so the dialog has a name.
  • Overusing ARIA: Fixing everything with ARIA instead of fixing bad HTML. → Do this instead: Use semantic HTML first; use ARIA only when necessary.
  • ARIA vs semantic HTML: Semantic HTML (button, nav, main, label) gives role and behaviour for free. ARIA supplements when you can’t use the right HTML (e.g. custom widget).
  • ARIA vs keyboard: Keyboard navigation is behaviour you implement; ARIA exposes role and state. Both are needed for custom components.
  • ARIA vs screen reader: Screen reader compatibility is the outcome; ARIA is one way to achieve it for custom UI.

Next step

Audit one custom component (e.g. a dropdown or modal). If it’s not using semantic HTML, add the correct ARIA role, name, and state, and implement the keyboard and focus behaviour from the ARIA APG. Test with a screen reader. Prefer replacing the custom control with a native element (button, select, dialog) where possible.