Accessible Ariel
Roles, Properties & States
ARIA lets you influence the accessibility tree directly — but only use it when native HTML isn't enough.
Click any rule to expand it. Tags show which W3C WAI section it covers.
A quick overview of the three concepts ARIA gives you control over.
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.
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.
Everything ARIA can do falls into one of these three categories.
What is this element?
role="dialog"role="tab"role="listbox"What are its characteristics?
aria-required="true"aria-haspopup="true"aria-label="Close"What's its current condition?
aria-expanded="false"aria-checked="true"aria-disabled="true"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 2Using ARIA on a div doesn't give it keyboard support. You still have to wire everything yourself.
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>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-disabledThe 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>An icon button with no visible text needs aria-label. Without it, a screen reader just says 'button' with no context.
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>Four rapid-fire patterns that break accessibility — with the correct alternative for each.
// ✗ 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>// ✗ 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>// ✗ 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>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>Watch the complete What is ARIA? tutorial on YouTube.
Every rule from this tutorial, mapped to the pattern it covers.