QuestionsAccessibility

ARIA roles, states, properties — and the first rule of ARIA

ARIAMediumAccessibility

What do ARIA roles, states, and properties actually do? Why is 'no ARIA better than bad ARIA'?

What it tests

That ARIA changes only the accessibility tree — never behaviour — and is easy to get dangerously wrong.

Approach & answer

ARIA (Accessible Rich Internet Applications) is a set of attributes that override or supplement what the accessibility tree exposes. Three kinds: ROLES say what a thing is (role="tablist", role="dialog"); STATES are dynamic and change (aria-expanded, aria-checked, aria-selected, aria-disabled); PROPERTIES are more static relationships/config (aria-label, aria-labelledby, aria-describedby, aria-controls, aria-haspopup). The critical mental model: ARIA changes ONLY the semantics reported to assistive tech. It adds no behaviour, no focusability, no keyboard handling, no styling. Slapping role="button" on a <div> makes a screen reader announce 'button' but the div still isn't focusable and still ignores Enter/Space until you add tabindex and key handlers yourself. This is why 'no ARIA is better than bad ARIA': incorrect ARIA actively lies to users — a role="checkbox" with no aria-checked, or an aria-expanded you forget to update, is worse than a plain element, because the user is told a state that doesn't match reality. The rules of thumb (the ARIA Authoring Practices): (1) prefer a native element with the semantics you need over ARIA; (2) don't change native semantics unless you must (don't put role="heading" on a <button>); (3) all interactive ARIA widgets must be keyboard-operable; (4) don't use role="presentation" or aria-hidden="true" on a focusable element (you'd hide something a keyboard user can still reach). Keep aria-* states in sync with the DOM on every change — that's the recurring bug.

Use this technique when

Reaching for a role/aria-* attribute; auditing custom widgets for state that drifts out of sync.

Code

<!-- BAD ARIA: announces 'button' but not focusable, no keys, worse than nothing -->
<div role="button">Menu</div>

<!-- If you MUST use a div, you own all of it -->
<div role="button" tabindex="0"
     onclick="toggle()" onkeydown="if(event.key==='Enter'||event.key===' ')toggle()">
  Menu
</div>

<!-- State must track reality: update aria-expanded on every toggle -->
<button aria-expanded="false" aria-controls="menu" onclick="toggle()">Menu</button>
<ul id="menu" hidden>…</ul>

<!-- Best: a native <button> gives role + focus + keyboard for free -->

References