What do ARIA roles, states, and properties actually do? Why is 'no ARIA better than bad ARIA'?
That ARIA changes only the accessibility tree — never behaviour — and is easy to get dangerously wrong.
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.
Reaching for a role/aria-* attribute; auditing custom widgets for state that drifts out of sync.
<!-- 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 -->