ARIA: Accessible Rich Internet Applications, Roles, States
ARIA adds accessibility info to dynamic UIs via roles, states, properties — for screen readers when HTML alone isn't enough.
What is ARIA?
ARIA (Accessible Rich Internet Applications) is a W3C specification that adds accessibility information to web pages and applications via HTML attributes. It tells assistive technologies (screen readers, voice control, switch devices) what dynamic UI elements are, their state, and how they behave — when HTML alone doesn't carry that meaning.
ARIA was created because HTML elements like <button>, <input>, <a> are accessible by default, but custom widgets (modals, tabs, autocomplete, drag-drop, tree views) built with <div> and <span> are invisible to assistive tech without help. ARIA bridges that gap.
The three categories of ARIA attributes
| Type | Purpose | Example |
|---|---|---|
| Roles | What the element IS | role="button", role="tablist" |
| Properties | Attributes describing the element | aria-label="Close", aria-haspopup="true" |
| States | Current dynamic state | aria-expanded="true", aria-checked="false" |
The first rule of ARIA: don't use ARIA
If a native HTML element exists with the semantics you need, use it. Native elements are accessible by default; ARIA is harder to get right.
<!-- BAD: ARIA on div -->
<div role="button" tabindex="0" onclick="..">Submit</div>
<!-- GOOD: native button -->
<button onclick="..">Submit</button>ARIA is for things HTML can't express natively (custom tabs, comboboxes, etc.).
Common ARIA roles
| Role | For |
|---|---|
role="button" | Custom buttons (when not using <button>) |
role="dialog" | Modal dialogs |
role="tablist" + role="tab" + role="tabpanel" | Tab UI |
role="menu" + role="menuitem" | Application menus |
role="combobox" | Autocomplete inputs |
role="tree" | Tree views |
role="alert" | Error messages, important notifications |
role="status" | Non-critical status updates |
role="navigation" | Navigation regions (or use <nav>) |
role="main" | Main content (or use <main>) |
Common ARIA properties
| Property | Use |
|---|---|
aria-label | Accessible name (when no visible label) |
aria-labelledby | Reference an element that names this |
aria-describedby | Reference a longer description |
aria-hidden="true" | Hide from assistive tech |
aria-haspopup | Element triggers a popup |
aria-controls | Element controls another by ID |
aria-current | Current item in a set (page, step, etc.) |
Common ARIA states
| State | Use |
|---|---|
aria-expanded | Disclosure / accordion / dropdown open? |
aria-selected | Tab / option selected? |
aria-checked | Checkbox / radio state |
aria-disabled | Element disabled? |
aria-pressed | Toggle button state |
aria-busy | Element being updated |
aria-invalid | Form field has invalid value |
Live regions: announce dynamic updates
Tells screen readers when content changes (e.g., "message sent", error appeared).
<!-- Polite: wait for current speech to finish -->
<div aria-live="polite" aria-atomic="true">
{{ savedMessage }}
</div>
<!-- Assertive: interrupt immediately (use sparingly) -->
<div aria-live="assertive">
{{ errorMessage }}
</div>
<!-- role="alert" is shorthand for aria-live="assertive" -->
<div role="alert">Error: connection lost</div>Real-world example: accessible tabs
<div role="tablist" aria-label="Account sections">
<button role="tab" aria-selected="true" aria-controls="profile-panel" id="profile-tab">
Profile
</button>
<button role="tab" aria-selected="false" aria-controls="billing-panel" id="billing-tab">
Billing
</button>
</div>
<div role="tabpanel" id="profile-panel" aria-labelledby="profile-tab">
Profile content
</div>
<div role="tabpanel" id="billing-panel" aria-labelledby="billing-tab" hidden>
Billing content
</div>ARIA best practices
- Use native HTML first. Don't recreate
<button>with ARIA. - Don't change native semantics. Don't put
role="button"on a<a>. - All interactive ARIA must be keyboard-accessible. Tabindex + key handlers.
- Don't hide focusable elements.
aria-hidden="true"on a button = broken. - Provide visible labels.
aria-labelis for icon-only buttons; visible text is better. - Test with screen reader. NVDA, JAWS, VoiceOver — what you intended ≠ what they announce.
- Follow APG patterns. WAI-ARIA Authoring Practices Guide has tested patterns.
- Update states as UI changes.
aria-expandedmust flip when accordion opens.
Common ARIA pitfalls
- Wrong ARIA over right HTML. Most common mistake.
- aria-hidden on focusable elements. Element is focusable but invisible to AT = confusing.
- Stale states.
aria-expanded="false"stays as menu opens. - Overuse of role="application". Disables browser shortcuts; rarely correct.
- Live regions firing too often. Annoying; use sparingly.
- Missing keyboard support. ARIA without keyboard = unusable.
- aria-label conflicting with visible text. Confusing; match them or omit.
FAQ: ARIA
Should I use ARIA on every element?
No — only when native HTML doesn't express the semantics you need. Native HTML is accessible by default.
What's the "first rule of ARIA"?
"Don't use ARIA" — if a native HTML element does the job, use it instead.
Does ARIA help SEO?
Indirectly. Search engines may use semantic info; the bigger SEO win is from accessible markup overall.
What's WAI-ARIA APG?
Authoring Practices Guide — tested patterns for common widgets (combobox, dialog, tabs). Use these instead of inventing.
Can I use ARIA on SVG?
Yes. <svg role="img" aria-labelledby="title"> with a <title> child.
What's aria-current?
Marks the current item in a set: aria-current="page" on the active nav link.
How do I test ARIA?
Automated tools (axe, Lighthouse) catch some errors. Manual: keyboard nav + screen reader (NVDA, VoiceOver).
Test ARIA + accessibility with LoadFocus
LoadFocus runs Lighthouse audits including the Accessibility audit, flagging many ARIA mistakes from 25+ regions. Sign up free at loadfocus.com/signup.
Related LoadFocus Tools
Put this concept into practice with LoadFocus — the same platform that powers everything you just read about.