ARIA attributes have strict rules about which roles they may appear on. The aria-allowed-attr audit catches ARIA states or properties used on roles that do not support them — for example, aria-checked on role="button". The WAI-ARIA specification only permits aria-checked on checkbox, menuitemcheckbox, menuitemradio, radio, switch, option, and treeitem. Misused ARIA is worse than no ARIA — it actively confuses assistive technology.
aria-allowed-attr (attribute disallowed on role), aria-valid-attr (attribute name not in spec / typo), aria-valid-attr-value (valid attribute, invalid value token).role + ARIA.Think of ARIA the way you think of legal grammar: aria-checked="mixed" is a real sentence, but only when the subject is a checkbox. Put the same word next to a button and the sentence stops parsing.
ARIA is the layer that tells assistive technology what each control IS, what STATE it's in, and what RELATIONSHIPS it has. When that layer disagrees with the role of the element, screen readers either ignore the attribute or — worse — announce a state that doesn't match what the user sees.
<button> with aria-checked="true"may be announced as either "button" or "checked" depending on the screen reader. Users get inconsistent behavior across NVDA, JAWS, VoiceOver, and TalkBack.aria-lable and aria-pressed-state are not in the spec. Browsers do not warn — the attribute is simply dropped. The visible UI looks fine; the screen-reader experience is broken.Most automated checkers (axe-core, the open-source rules engine behind many tools, plus the rules baked into Chrome DevTools' Issues panel) split ARIA validation into three separate checks. Knowing which one fired tells you what to fix.
The attribute name is real, but the role of the element does not support it. Example: aria-checked on role="button". The fix is either to change the role to one that allows the attribute (often switch or checkbox), or to drop the attribute and use the right native element.
The attribute itself does not exist. Usually a typo (aria-lable, aria-decribedby) or a made-up name (aria-pressed-state). Browsers ignore unknown ARIA attributes silently.
The attribute is real and allowed on this role, but the value is not one of the spec-permitted tokens. aria-pressed="yes" fails — the only valid values are "true", "false", and "mixed". aria-expanded="open" fails — must be "true" or "false".
Three of the most common ARIA validity failures, side by side:
<!-- BAD: aria-checked is not allowed on role="button" -->
<button aria-checked="true">Subscribe</button>
<!-- BAD: aria-lable is a typo; browsers silently drop it -->
<button aria-lable="Close dialog">×</button>
<!-- BAD: "yes" is not a valid value for aria-pressed -->
<button aria-pressed="yes">Bold</button>
<!-- GOOD: use role="switch" (or <input type="checkbox">) -->
<button role="switch" aria-checked="true">Subscribe</button>
<!-- GOOD: correct attribute name -->
<button aria-label="Close dialog">×</button>
<!-- GOOD: tokens must be true | false | mixed -->
<button aria-pressed="true">Bold</button>In every case, the simplest fix is a more honest description of what the element actually is. If it's a toggle, mark it as a switchor use a checkbox. If it's a button, drop the state attributes that don't apply.
role="..."<button>, <input type="checkbox">, <dialog>, and <details> handle role, state, focus, and keyboard for free. Recreating them with div + ARIA is where most validity bugs are born.
Before adding an ARIA attribute, check the role's entry in the WAI-ARIA spec on MDN. Every role lists exactly which states and properties are supported.
role="button" -> aria-pressed, aria-expanded, aria-disabled (no aria-checked)
role="checkbox" -> aria-checked, aria-required, aria-readonly
role="switch" -> aria-checked
role="tab" -> aria-selected, aria-controls
role="slider" -> aria-valuenow, aria-valuemin, aria-valuemax, aria-valuetextARIA boolean attributes accept "true" / "false" (sometimes "mixed"). Not "yes", not "1", not "on". aria-live accepts "off" / "polite" / "assertive". aria-level accepts an integer 1–9.
aria-expanded on a plain <div> with no interactive role is meaningless. State attributes belong on focusable controls with the matching widget role.
aria-labelledby, aria-describedby, aria-controls, and aria-owns all reference IDs. A reference to a missing ID is silently dropped.
<button disabled aria-disabled="true"> is redundant — disabled already does this. <nav role="navigation"> repeats what <nav> says. Pick one.
aria-expanded set to "false" at render and never updated is a common bug. Toggle it whenever the visual state toggles.
function Disclosure({ children, label }) {
const [open, setOpen] = useState(false);
const id = useId();
return (
<>
<button aria-expanded={open} aria-controls={id} onClick={() => setOpen(o => !o)}>
{label}
</button>
<div id={id} hidden={!open}>{children}</div>
</>
);
}Roles like widget, roletype, command, and composite are abstract — they exist to describe the role taxonomy and must never be used on real elements.
The eslint-plugin-jsx-a11y rules aria-props, aria-proptypes, aria-unsupported-elements, role-supports-aria-props, and role-has-required-aria-props catch the entire family of validity errors before merge.
aria-hidden="true" on a focusable element is a special class of validity error: the element stays in the tab order but is invisible to assistive tech. Either remove it from the tab order with tabindex="-1", or remove aria-hidden.
aria-checked on a Plain ButtonWhat's happening: A <button> is being used as a toggle. Developer added aria-checked="true", but checked is not a state on the button role.
Fix: Use aria-pressed for press-toggle buttons, or change the role to role="switch" if it's a settings toggle.
aria-required on a Non-Form ElementWhat's happening: aria-required is only valid on form-input roles (textbox, checkbox, radio, combobox, listbox, spinbutton, tree, radiogroup, gridcell).
Fix: Move it onto the actual input. Better yet, use the native HTML required attribute.
What's happening: aria-lable, aria-decribedby, aria-haspopup-menu. The browser ignores them silently.
Fix: Add eslint-plugin-jsx-a11y to your build with the aria-props rule enabled — it catches every typo in the attribute name.
What's happening: aria-pressed="yes", aria-expanded="open", aria-checked="1". Screen readers fall back to ignoring the state.
Fix: Always emit the literal strings "true" / "false" (or "mixed" where allowed). In React, pass a JS boolean — React serializes it correctly.
All three audits sound similar but catch different bugs. Knowing which fired tells you whether to fix the role, the attribute name, or the value token.
| Audit | What It Catches | Example Failure | Typical Fix |
|---|---|---|---|
aria-allowed-attr | Real attribute, used on a role that does not support it | <button aria-checked="true"> | Change role (e.g. to switch) or drop the attribute |
aria-valid-attr | Attribute name not in the WAI-ARIA spec (typo, made-up) | <img aria-lable="Logo"> | Correct the spelling — usually aria-label, aria-describedby, etc. |
aria-valid-attr-value | Real attribute, but value is not in the allowed token list | aria-pressed="yes" | Use spec tokens: "true" / "false" / "mixed" |
aria-required-attr (related) | A role is missing an attribute the spec marks as required | role="slider" with no aria-valuenow | Add the required state/property for the role |
It verifies that every ARIA state and property on an element is permitted for that element's role. The WAI-ARIA spec defines a fixed list of supported states and properties for each role, and anything outside that list fails the audit.
Yes. Invalid ARIA almost always maps to WCAG 2.2 Success Criterion 4.1.2 Name, Role, Value (Level A), because the role/state/value triple becomes unreliable. Some misuses also touch SC 1.3.1 Info and Relationships.
Because most ARIA in the wild is misused. WebAIM's 2025 Million homepage analysis found pages using ARIA averaged more detected errors than pages without it — the ARIA was being applied incorrectly. The W3C's First Rule of ARIA exists for exactly this reason: native HTML first, ARIA only when there's no native equivalent.
aria-pressed belongs on toggle buttons (Bold, Italic, Mute). aria-checked belongs on roles that represent a selection state — checkbox, radio, switch, menuitemcheckbox, menuitemradio, option, treeitem. Putting aria-checked on a button is the canonical aria-allowed-attr failure.
No. Unknown attributes (aria-lable, aria-pressed-state) are silently ignored by the HTML parser. The page renders normally and the screen-reader output is wrong. The only reliable way to catch typos is automated linting (eslint-plugin-jsx-a11y), an automated accessibility checker like axe-core, or Chrome DevTools' Issues panel.
Indirectly. AI answer engines use the accessibility tree and semantic HTML to understand page structure. Broken ARIA produces a noisier accessibility tree, weaker structured signals, and worse rankings in traditional Google search — and AI answer engines preferentially cite pages that already rank well. Clean ARIA helps both audiences.
Pick the right semantics for what the control actually does. If it's a settings toggle, use role="switch" — that role accepts aria-checked. If it's a press-toggle (Bold, Mute), swap to aria-pressed. If it's a real checkbox, use <input type="checkbox"> and skip ARIA entirely.
Allowed-attribute errors are almost always a sign that an element is wearing the wrong role. The fix is rarely "add more ARIA" — it's usually "use the native HTML element" or "change the role to one that actually fits." Once the role matches the behavior, the allowed-attribute list takes care of itself.
Run a Greadme deep scanto see exactly which elements on your site are using disallowed ARIA attributes, which are using attribute names that don't exist, and which are using invalid value tokens — then fix the templates that repeat the same mistake.