States and widget properties
A state is a fact about a control that can change while the page is open: checked, expanded, invalid, at 40 percent. ARIA attributes report that fact. They never cause it. Your script updates the picture and the attribute together.
In one minute
- Native first:
<input type="checkbox">,<details>,required,disabledand<input type="range">already report their own state. Add ARIA only on widgets you built. - Two jobs, one change: when something changes, update the visuals and the attribute in the same function.
- Attributes come in four families: toggle and selection, availability and validity, range values, and text and popups. This page has a card for each (23 in total).
- Setting
aria-expanded="true"does not open anything, andaria-invaliddoes not validate anything.
A state is the label on a door that says "Open" or "Closed". If the door opens and the sign still says "Closed", people walk away. ARIA states are that sign for people who cannot see the door. The attribute never opens the door: you do, and then you flip the sign.
1. See the tree change: pressed, expanded, value
Here are three widgets side by side. Use them with the mouse or the keyboard. The panel below the controls shows what the accessibility tree holds and what a screen reader would say after each change.
Step 1. Press Pin, press Details, drag Volume (or use the arrow keys).
Ships in two days.
| Control | Attribute in the tree |
|---|
aria-controls links the button to the panel it shows. Support is uneven, so never rely on it alone: aria-expanded is what
matters. The range input is native, so its value is already exposed. aria-valuetext only improves how the number is spoken.
2. Three states for one checkbox: true, false, mixed
A parent checkbox for a group is mixed when only some children are checked. A native checkbox does this with
the script property indeterminate. A custom one uses aria-checked="mixed". Tick the children and watch the parent.
Step 2. Tick some toppings, or click the parent (or press Space on it).
Do not put aria-checked on a native <input type="checkbox">. The browser already exposes the state, and a second value can disagree with it.
aria-pressed is for toggle buttons (Pin above). It can be mixed too, but it is not a checkbox.
3. Disabled, read-only, and what keyboard users can reach
"Cannot change" has three meanings, and each behaves differently for a keyboard user. Choose one and press Tab to see if you can land on it.
| Feature | Result |
|---|
Prefer a native disabled when there is nothing to explain. Prefer aria-disabled when focus should stay so the reason can be
read. With aria-disabled your handler must check the flag, because the browser will not block the click.
4. Invalid fields and their error message
aria-invalid="true" says "this value is wrong". It does not say why. Link the explanation with
aria-errormessage or aria-describedby. Type something in the field (try hello), press Check, and switch between
the three versions.
✕ Enter an email like name@example.com.
aria-errormessage is meant for exactly this, but screen reader support has been uneven, so many teams use aria-describedby too, or instead. Show the error
text visibly and never rely on the red colour alone (WCAG 1.4.1, 3.3.1). Use aria-invalid="grammar" or "spelling" only in editors, not for a missing email.
5. Range values: a number or a word?
A slider needs aria-valuenow, aria-valuemin and aria-valuemax. When the bare number would be unhelpful ("3"),
add aria-valuetext with the word you want spoken ("Wednesday"). This slider is built from a div so you can see every part.
Focus it and press the arrow keys, Home or End.
Keep aria-valuenow inside the min and max. For an indeterminate progress bar, omit aria-valuenow rather than invent a percentage. If you set
aria-valuetext, update it every time aria-valuenow changes.
6. Every state at a glance
Filter by family or type a word ("popup", "table", "number"). Each card says what values it takes, where it goes, what it does, and the usual trap.
Common mistakes
| You see | Why it is a problem | Fix |
|---|---|---|
aria-checked on a native checkbox | Two sources of truth that can disagree. | Remove it. Use checked or indeterminate. |
| Class changes, attribute does not | Screen reader hears the old state. | Set the attribute in the same function. |
aria-pressed on a button that does not stay on | Announces "not pressed" forever. | Remove it. |
aria-selected used for "current page" | Wrong meaning. | Use aria-current="page". |
aria-invalid with no message | User knows it is wrong, not why. | Add visible text and link it. |
aria-required relied on for validation | It only labels. It enforces nothing. | Use required or a submit handler. |
aria-valuenow outside min and max | Meaningless readout. | Clamp the value. |
- Press Pin and Details with the keyboard (Tab, then Space). In devtools, watch pressed and expanded flip in the Accessibility panel.
- In lab 5, choose "Number only", then "With aria-valuetext". Which would you rather hear?
- In the card reference, search "popup". Which attribute announces that a menu is coming, and what does it never do?
Remember
- ARIA states report. Your script changes the UI and the attribute together.
- Native inputs already expose their state. Do not double up.
disabledleaves the tab order,aria-disabledstays focusable,readonlyis focusable and copyable.- An invalid field needs visible error text linked to it. Colour is not enough.
- Use
aria-valuetextwhen a bare number is not what people should hear.