ARIA, and its five rules

Know what ARIA changes, what it cannot change, and when not to reach for it.

09of 16
≈ 13Min read
5Parts

ARIA changes the tree, and nothing else

role, aria-* and their friends edit what assistive technology is told about an element. They add no behaviour whatsoever: no keyboard handling, no focus, no click, no styling. role="button" on a div makes a screen reader say "button" about something that still does nothing when you press Enter.

That is the source of most ARIA bugs: it is a promise about behaviour you then have to keep yourself.

The five rules, as they are actually written

  • 1. Do not use ARIA if a native element with the semantics and behaviour you need already exists. <button> over role="button", every time.
  • 2. Do not change native semantics unless you really must. <h2 role="tab"> throws away the heading; wrap instead: <div role="tab"><h2>…</h2></div>.
  • 3. Every interactive ARIA control must be usable with the keyboard — and with the keys its pattern specifies.
  • 4. Do not put role="presentation" or aria-hidden="true" on a focusable element. You get something that is reachable by Tab and invisible to the person tabbing to it.
  • 5. Every interactive element needs an accessible name.
No ARIA is better than bad ARIA

A wrong role is worse than none: it tells a confident lie. WebAIM’s annual survey finds home pages with ARIA average more detected errors than pages without — not because ARIA is bad, but because it is usually applied without the behaviour it promises.

What it is genuinely for

NeedARIA
A state HTML has no attribute foraria-expanded, aria-selected, aria-current="page"
A relationship between elementsaria-labelledby, aria-describedby, aria-controls, aria-owns
A widget HTML does not haverole="tablist", role="combobox", role="tree"
Announcing a changerole="status", role="alert", aria-live
Hiding decorationaria-hidden="true" — on things that are not focusable

States that must be kept true

An aria-expanded that never changes is worse than none: it says "collapsed" while the panel is open. Anything you assert in ARIA has to be updated in the same function that changes the reality.

the attribute is the state — if it drifts, it lies
<button aria-expanded="false" aria-controls="panel">Specification</button>
<div id="panel" hidden>…</div>

<script>
button.addEventListener("click", () => {
  const open = button.getAttribute("aria-expanded") === "true";

  button.setAttribute("aria-expanded", String(!open));   // update BOTH
  panel.hidden = open;
});
</script>

The mistakes to recognise

  • role="button" on an actual <button> — redundant, and a sign somebody is guessing.
  • aria-label on a plain <div> or <span> with no role: most of the time it is simply ignored.
  • aria-hidden="true" on a wrapper that contains a link or a button — rule 4, and the commonest serious ARIA bug.
  • role="presentation" on a <table> used for layout — fine, except the table is probably data.
  • aria-live on something that updates constantly, so the user is interrupted permanently.
  • title used as the only name: not shown on touch, not read by every reader.

Practice