Who it affects
Someone using a screen reader hears a state that does not fit the control, like expanded on a checkbox, or misses the one state that mattered because it was written with the wrong attribute.
Someone using a braille display reads a short state code next to the control that is wrong or missing, and has no voice to double-check it against.
A developer three months from now sees an attribute that looks deliberate, trusts it, and copies the bug into the next component.
Broken versus fixed
Both examples are live and rendered in a sandboxed frame. The broken one is deliberately broken.
It is plainly ticked on screen. A screen reader announces: Email me when my order ships, checkbox, not checked. aria-selected means nothing on a checkbox, so the tick is invisible.
Announced: Email me when my order ships, checkbox, checked. Same pixels, right attribute. (The demo is static markup, so clicking it will not toggle anything.)
Show the markup behind both examples
<style>body{font-family:system-ui;padding:16px;margin:0}.box{display:inline-flex;align-items:center;gap:10px}.sq{width:20px;height:20px;border:2px solid #2b6cb0;border-radius:4px;background:#2b6cb0;position:relative}.sq::after{content:"";position:absolute;left:6px;top:2px;width:5px;height:10px;border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg)}</style>
<div class="box" role="checkbox" aria-selected="true" tabindex="0">
<span class="sq"></span>
Email me when my order ships
</div>
<style>body{font-family:system-ui;padding:16px;margin:0}.box{display:inline-flex;align-items:center;gap:10px}.sq{width:20px;height:20px;border:2px solid #2b6cb0;border-radius:4px;background:#2b6cb0;position:relative}.sq::after{content:"";position:absolute;left:6px;top:2px;width:5px;height:10px;border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg)}</style>
<div class="box" role="checkbox" aria-checked="true" tabindex="0">
<span class="sq"></span>
Email me when my order ships
</div>
Test it by hand in two minutes
- Right click the control, choose Inspect, and read every attribute that starts with aria- on the element with the role attribute.
- In Chrome DevTools open the Accessibility pane (under the Elements tab). Attributes the browser does not understand for that role are missing from the computed properties list.
- Turn on VoiceOver (Cmd+F5 on a Mac) or NVDA on Windows and Tab to the control. Check the state you hear matches what you see: ticked, pressed, expanded, selected.
- Change the control (tick it, open it) and listen again. If the announcement never changes, the state is being written to an attribute the role ignores.
The fix
<div role="checkbox" aria-selected="true" tabindex="0">Email me when my order ships</div>
<div role="checkbox" aria-checked="true" tabindex="0">Email me when my order ships</div>
Find the attribute the role actually uses for that state and move the value across. If there is no such attribute, the role is probably the wrong one for what you are building.
Toggle button written with aria-checked
<button aria-checked="true">Bold</button>
<button aria-pressed="true">Bold</button>
A button does not understand aria-checked. aria-pressed is the attribute for a button that stays on or off.
Selection state on the listbox instead of the option
<ul role="listbox" aria-selected="true">
<li role="option">Standard delivery</li>
</ul>
<ul role="listbox">
<li role="option" aria-selected="true">Standard delivery</li>
</ul>
aria-selected belongs on the thing that is selected, the option, not on the container.
Common mistakes
- Adding a second attribute alongside the wrong one and leaving both, so two states can disagree.
- Changing the role to one that happens to allow the attribute, which breaks the announcement in a different way.
- Guessing attribute names from what sounds right (aria-selected for a tick, aria-open for a menu) instead of checking the role.
- Removing the attribute without replacing it, so the state is gone from the announcement entirely.
- Using a native input with a checked attribute and then also writing aria-checked, which can fall out of step.
WCAG criteria this rule maps to
axe-core tags this rule as WCAG 2.2 level A. It is one of the checks for:
- 4.1.2 Name, Role, Value (level A). Official W3C text.
The scanner reads the role and compares every aria- attribute against the list that role supports, so this one is caught reliably. It cannot tell which attribute you meant, so it may also raise aria-required-attr on the same element until the right one is in place. Automated tools find only part of the accessibility problems on a page; a pass on this rule is not a pass on the criterion.
Related rules
aria-prohibited-attr
aria-label and aria-labelledby only work on elements that can have a name: controls, landmarks, images, headings, tables and similar.
A
Required ARIA attributes must be provided for the role
aria-required-attr
Some ARIA roles only make sense with a state or value attached.
A
ARIA attributes must have valid names
aria-valid-attr
Browsers only act on attribute names that exist in the ARIA specification.
A
ARIA attributes must follow the conditions of the element's role
aria-conditional-attr
A few ARIA attributes are allowed on a role only under conditions.