ARIA
Date: 2026-08-17
ARIA (Accessible Rich Internet Applications) is a set of attributes that override what the accessibility tree reports about an element. Its own first rule is not to use it — a native element carries the role, the state, the keyboard behaviour and the focus handling for free, and ARIA supplies only the first two.
The five rules, and what they mean in practice
The specification states them; only the first and the last get quoted, so here they all are with the consequence attached:
1 USE A NATIVE ELEMENT if one exists with the semantics you need
→ <button> over <div role="button">. always
2 DON'T CHANGE NATIVE SEMANTICS unless you must
→ <h2 role="button"> breaks heading navigation for screen reader users
3 ALL INTERACTIVE ARIA CONTROLS MUST BE KEYBOARD OPERABLE
→ role="button" without tabindex and a key handler is a lie
4 DON'T USE role="presentation" OR aria-hidden ON A FOCUSABLE ELEMENT
→ produces something reachable by keyboard but invisible to
screen readers. the worst possible state
5 EVERY INTERACTIVE ELEMENT NEEDS AN ACCESSIBLE NAME
→ an icon button with no text and no aria-label is announced
as "button", which is useless
Rule 3 is the one that catches people. ARIA changes what is announced. It changes nothing about behaviour.
<!-- announced as a button. does nothing on Enter or Space.
not focusable. not in the tab order. -->
<div role="button" onclick="submit()">Continue</div>
<!-- to make that div behave, you must write ALL of this -->
<div role="button" tabindex="0"
onclick="submit()"
onkeydown="if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault(); submit();
}">Continue</div>
<!-- or, instead of all of it -->
<button type="button" onclick="submit()">Continue</button>The <button> gets focus, tab order, Enter and Space activation, the disabled state, form association, and the platform’s own focus ring — none of which ARIA provides.
The attributes worth knowing
| Attribute | Does | Note |
|---|---|---|
role | Overrides what the element is | Use sparingly |
aria-label | Supplies a name where there’s no visible text | Icon buttons |
aria-labelledby | Names it from other elements’ text | Preferred over aria-label where visible text exists |
aria-describedby | Attaches supplementary text | Error messages, hints |
aria-expanded | Open/closed state of a disclosure | The most-forgotten one |
aria-current | ”This is the current page/step” | Navigation, breadcrumbs |
aria-live | Announces dynamic changes | See below |
aria-hidden | Removes from the accessibility tree | Never on anything focusable |
aria-invalid | Marks a field as failing validation | Pair with aria-describedby |
Naming precedence, since it causes confusion: aria-labelledby beats aria-label, which beats the element’s own text content. So an aria-label silently overrides visible text — and if the two disagree, a voice-control user asking for the button by its visible name can’t activate it.
Live regions
The one thing ARIA does that no native element does: announce a change that happens without a page navigation.
<!-- polite: waits for a pause. use for almost everything -->
<div aria-live="polite" role="status">Added to basket</div>
<!-- assertive: interrupts immediately. errors and warnings ONLY -->
<div aria-live="assertive" role="alert">Payment declined</div>The element must exist in the DOM before the content changes. Inserting a live region and its text at the same moment usually announces nothing — render the empty container on load, then update its contents.
This is essential for single-page commerce: adding to basket, applying a discount code, filtering results and validation failures all change the page silently for a screen reader user without one — Screen Readers, Feedback and System Status.
Where ARIA is genuinely required
Native HTML doesn’t cover everything, and these need it:
- Tabs, accordions, comboboxes, tree views — no native equivalent. Follow the ARIA Authoring Practices patterns rather than inventing the attribute set, and use a tested headless component library rather than writing them
- Live regions, as above
- State on custom widgets —
aria-expanded,aria-selected,aria-pressed - Relationships the DOM order doesn’t express —
aria-describedbylinking a field to an error message elsewhere on the page — Accessible Forms - Landmark regions where the native elements aren’t available, though
<nav>,<main>,<aside>and<header>cover most of it — Semantic HTML
The failure worth naming
Bad ARIA is worse than no ARIA. No ARIA means a screen reader falls back to the element’s real semantics, which are at least honest. Wrong ARIA actively misinforms.
<div role="button"> announced as a button.
no tabindex, no key handler cannot be reached or activated by keyboard.
→ a keyboard user is told there's a control
and given no way to use it
<span aria-hidden="true"> hidden from screen readers.
<button>Buy</button> still focusable by keyboard.
</span> → tab lands on something that announces
nothing at all
Both are common, both come from adding ARIA to look thorough, and both are more damaging than the plain markup would have been.
Checking it
- Automated tooling catches perhaps a third of issues — invalid attribute values, missing names, contrast. Necessary, not sufficient — Accessibility Testing
- Tab through the page. Everything interactive reachable, focus visible, order sensible — Keyboard Navigation, Focus Management
- Inspect the accessibility tree in DevTools, which shows what’s actually reported rather than what you wrote — The Accessibility Tree
- Listen to it. Fifteen minutes with a real screen reader is worth more than any audit report
Where it interacts
- The Accessibility Tree — the structure ARIA modifies, and the thing to inspect when it doesn’t behave
- Semantic HTML — the alternative that rule 1 points at, and the right answer most of the time
- WCAG — the conformance standard ARIA is used to meet
- Web Components — shadow DOM breaks ID-based ARIA relationships, which is a real constraint on custom widgets