ARIA is a set of attributes that tell assistive technology an element's role, state, and relationships. The key word is tell. ARIA draws nothing and adds no behavior: it changes only how an element appears in the accessibility API - the accessibility tree the browser builds for screen readers. A newcomer to accessibility reacts naturally: sprinkling the markup with attributes - a role here, an aria-label there, an aria-hidden wherever something is in the way. It feels like more ARIA means more accessible. In practice the opposite.
The first rule of ARIA: if a native HTML element or attribute already provides the semantics and behavior you need, use it instead of repurposing another tag with a role. Behind the rule sits a sobering statistic: in WebAIM's survey of a million home pages, pages with ARIA averaged noticeably more accessibility errors than pages without it. Hence the saying: no ARIA is better than bad ARIA. The reason is simple: a native button brings focusability, Enter and Space handling, the button role, and correct state - free, in every browser. Replace it with a div carrying role=button and the whole baggage must be reproduced by hand.
This is where the main trap hides. A declared role is not decoration - it is a contract. Declare role=dialog and you owe focus trapping in the window, returning focus to the trigger on close, and Escape handling. Declare role=tab and you take on arrow-key navigation and managing aria-selected. ARIA describes what an element pretends to be, but delivering the promise is your code's job. A screen reader announces a div with role=button as a button, the user presses Enter and gets nothing: there is no handler. That is worse than an honest, unannounced div - the interface has lied.
ARIA has three kinds of attributes, and telling them apart helps. A role sets the object type - a dialog, a tab, a status. A state describes a changeable condition - whether a list is expanded, whether an item is selected. A property records a relationship or characteristic - which element the button controls, which heading names it.
| Mechanism | What it describes | Example |
|---|---|---|
| Role | Object type | dialog, tab, status |
| State | Changeable condition |
| aria-expanded, aria-selected |
| Property | Relationship or characteristic | aria-controls, aria-labelledby |
|---|
Take a product page in a multilingual shop. A Filters button collapses and expands a panel. ARIA belongs here: HTML has no native disclosure section with the right semantics. The button stays a native button - keyboard and role for free - while two attributes express what HTML cannot: aria-expanded reports the state, aria-controls links the button to the panel. On click your code both toggles hidden on the section and flips aria-expanded to true. The attribute and the real state must always match, or the screen reader announces a lie.
The name a screen reader speaks for an element is computed by the browser through a strict algorithm - the accessible name computation. It has precedence: aria-labelledby overrides aria-label, which overrides a native label such as the label element or the element's own content, and only last comes title. Hence the rule: visible text is almost always preferable to aria-label. A visible label is read by everyone - sighted and blind alike - it does not drift out of sync with the interface when translated, and does not go stale silently. aria-label earns its place where there is physically no visible text: an icon button with no caption. Using it to replace clear text means creating two diverging versions of the truth.
A separate mechanism is live regions. The aria-live attribute makes a screen reader announce changes in a block without moving focus there: aria-live=polite waits for a pause and suits statuses like Item added to cart, while aria-live=assertive interrupts and is reserved for errors. A typical failure: the team hides a decorative cart icon with aria-hidden and, in the same stroke, hides the item counter inside it - and the blind shopper stops hearing how many are in the cart. ARIA did exactly what was written; what was written was wrong. Hence the discipline: native HTML first, ARIA only when the platform cannot express what you need, and every attribute backed by code that keeps its promise.
<button
aria-expanded="false"
aria-controls="filters"
>Filters</button>
<section id="filters" hidden>…</section>