The ARIA model
ARIA is a set of attributes that tell assistive technology what something is when HTML cannot say it.
It adds a role, a name and a state. It adds no behaviour: role="button" does not listen for Enter or Space.
You still have to.
In one minute
- First rule: if a native element already does the job (
<button>,<a href>,<input>,<select>,<details>,<table>), use it. No ARIA needed. - Every element ends up with a role (what it is), a name (what it is called) and a value or state (how it is right now).
- ARIA changes what is announced. It never moves focus, never handles keys and never makes something clickable.
- If script changes the UI, the same script must change the ARIA attribute, or the tree stays out of date.
ARIA is a name tag, not a skill. Pinning a "Lifeguard" tag on someone does not teach them to swim. Everyone now expects them to rescue people, and when they cannot, it is worse than no tag. Put the tag only on things that can do the job.
1. The first rule: use the native element
A native element gives you a role, a name, keyboard support and a focus ring without a single aria-*
attribute. A div with a role gives you only the role. Try to rebuild a button by hand: tick each fix and see what
is still missing. Then use the real element.
Step 1. Choose the element, then press Tab to the control and try Enter and Space.
Nothing pressed yet.
| Feature | Result |
|---|
Adding role="button" to a real <button> is noise. Replacing the button with a div and a role
is a regression unless you rebuild focus, activation, the name and the disabled state yourself. Use ARIA when you build
a widget HTML does not have (a tablist, a combobox, a tree), or when you must expose a state HTML cannot, such as
aria-expanded on a disclosure that is not <details>. Lessons 16 to 21 cover those cases.
2. Name, role and value
Assistive technology does not read your CSS. It reads the accessibility tree: a simplified copy of the page that the browser builds from the DOM. In that tree each control should answer three questions.
- Role: what kind of thing is it? Button, checkbox, heading, navigation. It comes from the HTML element or from
role. - Name: what is it called? Visible text,
<label>,alt,aria-labeloraria-labelledby. Lesson 16 covers naming fully. - Value and state: is it checked, pressed, expanded, invalid, at 40 of 100? The attributes are in lesson 17.
This is WCAG 4.1.2 Name, Role, Value. Below is a hand-made checkbox. Click it, and compare "visual only" with "visual plus
aria-checked".
Step 2. Choose how the script behaves, then toggle the box with Space or a click.
A native <input type="checkbox"> keeps picture and state in sync by itself. A custom one makes you do it.
3. The host language wins, until you override it
The host language is HTML. A <button> already has the role button. A <nav> is a
navigation landmark. The role attribute can override that, so you must be careful. Pick a snippet and see
what the tree gets.
Step 3. Choose a snippet.
role="none" and role="presentation" strip semantics. Use them on a container whose children carry the
real meaning, never on a focusable control.
Some roles hide their own children from the tree: button, checkbox, img, math,
menuitemcheckbox, menuitemradio, option, progressbar, radio, scrollbar,
separator, slider, switch and tab. A heading inside a role="button" is just text. Do not use
aria-owns on these to rescue the children. If you need the children exposed, you picked the wrong role.
4. Abstract roles are not for pages
The ARIA specification keeps a family tree of roles so they can inherit properties from each other. The top-level "parent" roles are abstract: they exist only for that model. You must not use them in HTML. Type a role to test it.
Step 4. Type a role name, or pick an example.
role="widget" does nothing useful for a screen reader. Pick a concrete role from lessons 18 to 20 instead.
5. Implicit values and explicit attributes
Many properties have a default. If you leave the attribute off, assistive technology uses that default, it does not treat it as "unknown". Some roles change the default. Orientation is a good example: arrow keys follow it.
| Role | Default orientation |
|---|---|
slider, separator, tablist, toolbar, menubar | horizontal |
scrollbar, listbox, menu, tree | vertical |
Set the attribute explicitly whenever the user can change the state, or whenever your layout differs from the default. That way your script and the tree cannot drift apart.
6. What ARIA never does
Most ARIA bugs come from believing ARIA does more than it does. Choose a claim and see whether it is true.
Step 5. Choose a claim.
Common mistakes
| You see | Why it is a problem | Fix |
|---|---|---|
<div role="button"> with no handler | Announced as a button, does nothing from the keyboard. | Use <button>. |
<button role="button"> | Redundant noise. | Remove the role. |
<a href role="button"> | Looks like a button, but Space does not work. | Pick one: link or button. |
role="widget" | Abstract role. Ignored. | Use a concrete role. |
| Script updates visuals only | The tree reports the old state. | Update the attribute in the same function. |
| Accessible name differs from the visible label | Voice-control and screen reader users meet different interfaces. | Keep them identical (WCAG 2.5.3). |
- Open devtools, select this page's skip link, and read its role and name in the Accessibility panel. There is no ARIA on it, and the tree is still correct.
- In lab 1, tick only
role="button"and press Tab. What is announced, and can you reach it? - Find a custom widget on a site you use. Does its state change in the tree when you click it?
Remember
- Native first. ARIA only where HTML has no element or no state for it.
- Role, name, value: the three answers every control must give the tree.
- ARIA changes announcements, not behaviour. Focus, keys and clicks are your job.
- Never use abstract roles, and never put a role on a focusable control to strip it.
- Keep attributes in sync with the UI in the same piece of code.