Accessible Ariel
What is ARIA?
Roles, Properties & States
ARIA lets you influence the accessibility tree directly — but only use it when native HTML isn't enough.
The Rules
Click any rule to expand it. Tags show which W3C WAI section it covers.
What We'll Cover
A quick overview of the three concepts ARIA gives you control over.
What is ARIA?
- What is ARIA?
- When you need it
- When you don't
ARIA Stands For…
Accessible Rich Internet Applications — a W3C specification for enriching the accessibility tree.
A set of HTML attributes maintained by the W3C — giving assistive technologies more information about what's on the page.
The Accessibility Tree
Screen readers don't see your page visually. They navigate a parallel structure called the accessibility tree.
heading "Welcome"navlink "Home"link "About"link "Blog"articletext "Article text…"button "Submit"
ARIA lets you influence the accessibility tree directly.
Roles, Properties & States
Everything ARIA can do falls into one of these three categories.
Roles
What is this element?
role="dialog"role="tab"role="listbox"
Properties
What are its characteristics?
aria-required="true"aria-haspopup="true"aria-label="Close"
States
What's its current condition?
aria-expanded="false"aria-checked="true"aria-disabled="true"
The First Rule of ARIA
Native HTML elements already carry roles, properties, and states for free. Prefer them.
The First Rule of ARIA
Don't use ARIA
if you don't have to.
<button>→ Announced as "button"<input type="checkbox">→ Has checked state built in<h2>→ Signals heading level 2
div[role=button] vs <button>
Using ARIA on a div doesn't give it keyboard support. You still have to wire everything yourself.
- Keyboard focus
- no
- yes
- Enter / Space handling
- no
- yes
- Announced as "button"
- yes
- yes
- Event wiring needed
- yes
- no
- Free
- no
- yes
Custom Dropdown
No native HTML element covers a styled listbox. Use aria-haspopup, aria-expanded, and role=listbox to describe the pattern — then watch the state inspector as you interact.
Live demo
aria-expandedfalsearia-selectednoneThe markup
<button
aria-haspopup="listbox"
aria-expanded={open}
aria-controls={listboxId}
>
Choose an option
</button>
<ul
id={listboxId}
role="listbox"
aria-labelledby={buttonId}
hidden={!open}
>
<li role="option" aria-selected={selected === "Option A"}>
Option A
</li>
</ul>Live Regions
When content changes without a page reload, use aria-live so screen readers announce the update. Type in the search box — a screen reader would announce the result count automatically.
Live demo — type to filter
aria-expandedaria-hiddenaria-labelaria-labelledbyaria-describedbyaria-livearia-atomicaria-requiredaria-invalidaria-checkedaria-selectedaria-haspopuparia-controlsaria-disabled
The markup
// polite — waits for current speech to finish
<div role="status" aria-live="polite" aria-atomic="true">
{count !== null && `Showing ${count} results`}
</div>
// assertive — interrupts immediately (use sparingly)
<div role="alert" aria-live="assertive">
{errorMessage}
</div>Icon Button Labels
An icon button with no visible text needs aria-label. Without it, a screen reader just says 'button' with no context.
aria-describedby
Point to any element on the page and its text becomes a description — read automatically by screen readers when the associated input is focused.
Live demo
Must be at least 8 characters and include a number.
Focus the input above to see what a screen reader would announce.
The markup
<input
type="password"
aria-describedby="pw-hint"
/>
<p id="pw-hint">
Must be at least 8 characters and include a number.
</p>What Not to Do
Four rapid-fire patterns that break accessibility — with the correct alternative for each.
Don't use ARIA to fix bad HTML
// ✗ Don't do this
<div role="list">
<div role="listitem">Item 1</div>
<div role="listitem">Item 2</div>
</div>
// ✓ Just use the right element
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>Don't duplicate semantics
// ✗ Redundant — <button> is already role="button"
<button role="button">Submit</button>
// ✗ Redundant — <h1> is already role="heading"
<h1 role="heading" aria-level="1">Title</h1>
// ✓ Just use the element
<button>Submit</button>
<h1>Title</h1>Never aria-hidden a focusable element
// ✗ Focus still lands here — SR skips it — confusing
<button aria-hidden="true">Submit</button>
// ✓ aria-hidden is fine on decorative SVGs
<svg aria-hidden="true" focusable="false">
<!-- decorative icon -->
</svg>
// ✓ To truly hide from everyone, use hidden
<button hidden>Submit</button>Keep aria state in sync
const [open, setOpen] = useState(false);
// ✗ aria-expanded hardcoded — always wrong after first click
<button aria-expanded="false" onClick={() => setOpen(o => !o)}>
Menu
</button>
// ✓ aria-expanded mirrors state
<button aria-expanded={open} onClick={() => setOpen(o => !o)}>
Menu
</button>Full Video Tutorial
Watch the complete What is ARIA? tutorial on YouTube.
Full ARIA Checklist
Every rule from this tutorial, mapped to the pattern it covers.
- Prefer native HTML elements over ARIA whenever possible
- role defines what the element is to assistive technology
- aria-label / aria-labelledby gives every interactive element an accessible name
- aria-describedby links hints, instructions, and errors to their input
- aria-expanded, aria-checked, aria-selected stay in sync with visual state
- aria-live regions announce dynamic content changes
- aria-hidden never applied to focusable elements
- Custom widgets implement full keyboard interaction (arrow keys, Enter, Escape)
- ARIA does not add behavior — keyboard handlers must be coded manually