How to Fix ARIA Misuse and Errors
ARIA (Accessible Rich Internet Applications) attributes are powerful tools for improving accessibility, but they are also among the most commonly misused features on the web. Incorrect ARIA does not just fail to help — it actively makes content less accessible by sending conflicting or misleading information to assistive technologies.
This guide covers the most frequent ARIA errors flagged by axe-core and how to fix each one with practical code examples.
The Five Rules of ARIA Use
Before reaching for any ARIA attribute, internalize the W3C's five rules. They exist because ARIA is a last resort, not a first choice.
- Rule 1: Don't use ARIA if native HTML works. A
<button>already has the button role, keyboard behavior, and focus management built in. Addingrole="button"to a<div>means you must manually recreate all of that. - Rule 2: Don't change native semantics unless you must. Adding
role="heading"to a<button>overrides its native button semantics and confuses assistive technology users. - Rule 3: All interactive ARIA controls must be keyboard accessible. ARIA only changes what assistive technologies announce — it does not add behavior. A
<div role="button">with notabindexor keyboard event handlers is invisible to keyboard users. - Rule 4: Don't hide focusable elements. Never apply
role="presentation"oraria-hidden="true"to elements that can receive focus. - Rule 5: All interactive elements must have an accessible name. Every button, link, and form control needs a name that assistive technologies can announce.
WCAG 4.1.2: Name, Role, Value
Most ARIA errors trace back to WCAG Success Criterion 4.1.2, which requires that for all user interface components, the name, role, and value can be programmatically determined, and that changes to these are communicated to assistive technologies. Every fix in this guide relates to satisfying this criterion.
aria-hidden="true" on Focusable Elements
axe-core rule: aria-hidden-focus
When you set aria-hidden="true" on an element, screen readers ignore it entirely. But if that element (or a child) can receive keyboard focus, sighted keyboard users will tab to an invisible element — they can interact with something the screen reader cannot announce.
<!-- Bad: focusable element hidden from screen readers -->
<div aria-hidden="true">
<button>Submit</button>
</div>
<!-- Good: remove aria-hidden, or make children inert -->
<div>
<button>Submit</button>
</div>
<!-- Good: if you need to hide the section entirely -->
<div aria-hidden="true">
<button tabindex="-1" disabled>Submit</button>
</div>
Invalid Role Values
axe-core rule: aria-roles
ARIA defines a fixed set of valid roles. Misspellings, made-up roles, or deprecated roles are silently ignored by browsers, meaning the element loses its intended semantics entirely.
<!-- Bad: "buton" is not a valid ARIA role -->
<div role="buton">Save</div>
<!-- Bad: "nav" is not a valid role (it's "navigation") -->
<div role="nav">...</div>
<!-- Good: use the correct role -->
<div role="button" tabindex="0">Save</div>
<!-- Better: use native HTML -->
<button>Save</button>
Missing Required ARIA Attributes
axe-core rule: aria-required-attr
Some ARIA roles require specific attributes to be present. Without them, assistive technologies cannot convey essential state information to users.
<!-- Bad: role="checkbox" requires aria-checked -->
<div role="checkbox">Accept terms</div>
<!-- Good: include the required attribute -->
<div role="checkbox" aria-checked="false" tabindex="0">Accept terms</div>
<!-- Bad: role="slider" requires aria-valuenow, aria-valuemin, aria-valuemax -->
<div role="slider">Volume</div>
<!-- Good -->
<div role="slider" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" tabindex="0">Volume</div>
<!-- Better: use native HTML -->
<input type="checkbox" id="terms">
<label for="terms">Accept terms</label>
Invalid ARIA Attribute Values
axe-core rule: aria-valid-attr-value
ARIA attributes accept specific value types. Passing an invalid value is equivalent to not setting the attribute at all, while potentially causing unexpected behavior in different screen readers.
<!-- Bad: aria-checked must be "true", "false", or "mixed" -->
<div role="checkbox" aria-checked="yes">Option</div>
<!-- Good -->
<div role="checkbox" aria-checked="true" tabindex="0">Option</div>
<!-- Bad: aria-live must be "off", "polite", or "assertive" -->
<div aria-live="true">Status updates</div>
<!-- Good -->
<div aria-live="polite">Status updates</div>
ARIA Attributes Not Allowed on an Element
axe-core rule: aria-allowed-attr
Not every ARIA attribute is valid on every role. Applying an attribute that conflicts with or is not supported by the element's role creates unpredictable behavior across assistive technologies.
<!-- Bad: aria-checked is not allowed on role="textbox" -->
<div role="textbox" aria-checked="true">...</div>
<!-- Good: only use attributes valid for the role -->
<div role="textbox" contenteditable="true" aria-multiline="false">...</div>
<!-- Bad: aria-expanded is not valid on role="img" -->
<div role="img" aria-expanded="true" aria-label="Chart">...</div>
<!-- Good -->
<div role="img" aria-label="Chart">...</div>
Missing Required Child Roles
axe-core rule: aria-required-children
Certain ARIA roles expect specific child roles to function correctly. A role="list" without role="listitem" children, or a role="tablist" without role="tab" children, breaks the expected interaction pattern.
<!-- Bad: tablist must contain tab children -->
<div role="tablist">
<div>Tab 1</div>
<div>Tab 2</div>
</div>
<!-- Good -->
<div role="tablist">
<button role="tab" aria-selected="true">Tab 1</button>
<button role="tab" aria-selected="false">Tab 2</button>
</div>
<!-- Bad: menu must contain menuitem children -->
<ul role="menu">
<li>Edit</li>
<li>Delete</li>
</ul>
<!-- Good -->
<ul role="menu">
<li role="menuitem">Edit</li>
<li role="menuitem">Delete</li>
</ul>
Missing Required Parent Roles
axe-core rule: aria-required-parent
Some roles only make sense within a specific parent context. A role="tab" outside a role="tablist" is meaningless to assistive technologies.
<!-- Bad: tab must be inside a tablist -->
<div>
<button role="tab">Settings</button>
</div>
<!-- Good -->
<div role="tablist">
<button role="tab" aria-selected="true">Settings</button>
</div>
<!-- Bad: listitem must be inside a list -->
<div role="listitem">Item 1</div>
<!-- Good -->
<div role="list">
<div role="listitem">Item 1</div>
</div>
Missing Accessible Names on Input Fields
axe-core rule: aria-input-field-name
Every ARIA input widget (role="textbox", role="combobox", role="searchbox", role="spinbutton") must have a discernible accessible name. Without one, screen reader users have no idea what the field is for.
<!-- Bad: no accessible name -->
<div role="searchbox" contenteditable="true"></div>
<!-- Good: use aria-label -->
<div role="searchbox" contenteditable="true" aria-label="Search articles"></div>
<!-- Good: use aria-labelledby -->
<span id="search-label">Search articles</span>
<div role="searchbox" contenteditable="true" aria-labelledby="search-label"></div>
<!-- Better: use native HTML -->
<label for="search">Search articles</label>
<input type="search" id="search">
Missing Accessible Names on Toggle Fields
axe-core rule: aria-toggle-field-name
Toggle-type ARIA widgets (role="checkbox", role="switch", role="menuitemcheckbox", role="menuitemradio") must also have accessible names.
<!-- Bad: no accessible name -->
<div role="switch" aria-checked="false" tabindex="0"></div>
<!-- Good -->
<div role="switch" aria-checked="false" tabindex="0" aria-label="Dark mode"></div>
<!-- Better: use visible label -->
<span id="dark-label">Dark mode</span>
<div role="switch" aria-checked="false" tabindex="0" aria-labelledby="dark-label"></div>
aria-label on Non-Interactive Elements
Applying aria-label to non-interactive elements like <div>, <span>, or <p> has inconsistent support across screen readers. Many screen readers will simply ignore it, meaning your intended label is never announced.
<!-- Bad: aria-label on a generic div -->
<div aria-label="Important notice">
<p>Your subscription expires tomorrow.</p>
</div>
<!-- Good: use a semantic element or visible text -->
<section aria-label="Important notice">
<p>Your subscription expires tomorrow.</p>
</section>
<!-- Also good: use a heading -->
<div>
<h3>Important notice</h3>
<p>Your subscription expires tomorrow.</p>
</div>
Redundant ARIA on Native HTML
Adding ARIA attributes that duplicate native HTML semantics is unnecessary code that can introduce errors during maintenance. Native HTML semantics are already communicated to assistive technologies.
<!-- Bad: redundant role -->
<button role="button">Save</button>
<nav role="navigation">...</nav>
<a href="/about" role="link">About</a>
<input type="checkbox" role="checkbox">
<!-- Good: native HTML is sufficient -->
<button>Save</button>
<nav>...</nav>
<a href="/about">About</a>
<input type="checkbox">
Conflicting ARIA Attributes
Some ARIA attribute combinations contradict each other, sending conflicting signals to assistive technologies. Screen readers may handle these conflicts differently, leading to inconsistent experiences.
<!-- Bad: aria-hidden="true" but also has aria-label -->
<button aria-hidden="true" aria-label="Close">X</button>
<!-- Good: pick one intent -->
<button aria-label="Close">X</button>
<!-- Bad: aria-disabled="true" with aria-required="true" -->
<input aria-disabled="true" aria-required="true">
<!-- Good: a disabled field should not be required -->
<input aria-disabled="true">
<!-- Bad: role="presentation" removes semantics, but aria-label adds them back -->
<table role="presentation" aria-label="User data">...</table>
<!-- Good: if it is presentational, don't label it -->
<table role="presentation">...</table>
<!-- Good: if it is a data table, don't make it presentational -->
<table aria-label="User data">...</table>
aria-expanded Without Toggle Behavior
The aria-expanded attribute signals that an element controls a collapsible section. If you add it without updating its value when the user interacts, or put it on an element with no toggle behavior, screen readers will announce a broken control.
<!-- Bad: aria-expanded set but never updated -->
<button aria-expanded="false">Menu</button>
<ul class="dropdown">...</ul>
<!-- Good: toggle the value with JavaScript -->
<button aria-expanded="false" aria-controls="menu-list" onclick="toggleMenu(this)">Menu</button>
<ul id="menu-list" hidden>...</ul>
<script>
function toggleMenu(btn) {
const expanded = btn.getAttribute('aria-expanded') === 'true';
btn.setAttribute('aria-expanded', String(!expanded));
document.getElementById('menu-list').hidden = expanded;
}
</script>
<!-- Bad: aria-expanded on a non-interactive element -->
<span aria-expanded="true">Details</span>
<!-- Good: use a native HTML element -->
<details open>
<summary>Details</summary>
<p>Content here.</p>
</details>
aria-live Overuse
The aria-live attribute creates live regions that announce content changes to screen reader users. Overusing it turns the experience into a stream of interruptions, making the page unusable.
<!-- Bad: entire page section as live region -->
<main aria-live="assertive">
<h1>Dashboard</h1>
<p>Welcome back.</p>
<div>...lots of content...</div>
</main>
<!-- Good: narrow live region around dynamic content only -->
<main>
<h1>Dashboard</h1>
<p>Welcome back.</p>
<div aria-live="polite">3 new notifications</div>
</main>
<!-- Bad: aria-live="assertive" for non-urgent updates -->
<div aria-live="assertive">Page 2 of 10</div>
<!-- Good: use "polite" for non-urgent information -->
<div aria-live="polite">Page 2 of 10</div>
Use aria-live="assertive" only for urgent information like error messages or time-sensitive alerts. For everything else, use aria-live="polite" so the announcement waits until the user is idle.
role="presentation" vs. aria-hidden="true"
These two attributes serve different purposes and are often confused.
- role="presentation" removes the semantic meaning of an element but keeps its content visible and accessible. A
<table role="presentation">is no longer announced as a table, but its text content is still read. - aria-hidden="true" hides the element and all its content from assistive technologies entirely. The content becomes invisible to screen readers.
<!-- Decorative icon: hide from screen readers -->
<span aria-hidden="true">★</span>
<!-- Layout table: remove table semantics but keep content -->
<table role="presentation">
<tr>
<td>Column 1 content</td>
<td>Column 2 content</td>
</tr>
</table>
<!-- Bad: using aria-hidden when you meant role="presentation" -->
<table aria-hidden="true">
<tr><td>Important content</td></tr>
</table>
<!-- This hides ALL content from screen readers -->
aria-describedby and aria-labelledby Pointing to Nonexistent IDs
When aria-describedby or aria-labelledby reference an ID that does not exist in the DOM, the attribute silently fails. The element ends up with no accessible name or description, and no error appears in the browser console.
<!-- Bad: referenced ID does not exist -->
<input type="email" aria-labelledby="email-lbl">
<!-- There is no element with id="email-lbl" -->
<!-- Good: ensure the referenced element exists -->
<label id="email-lbl">Email address</label>
<input type="email" aria-labelledby="email-lbl">
<!-- Bad: typo in the ID reference -->
<input type="password" aria-describedby="pasword-hint">
<p id="password-hint">Must be at least 8 characters.</p>
<!-- Good: IDs must match exactly -->
<input type="password" aria-describedby="password-hint">
<p id="password-hint">Must be at least 8 characters.</p>
<!-- Better: use native HTML for simple cases -->
<label for="email">Email address</label>
<input type="email" id="email">
Common causes of broken ID references include dynamic content where the referenced element is rendered conditionally, copy-paste errors where IDs were changed in one place but not the other, and component frameworks where IDs are auto-generated differently on each render.
Testing with Browser Accessibility Inspector
All major browsers have built-in accessibility tools that let you inspect the computed ARIA properties of any element. These tools show you exactly what assistive technologies will see.
Chrome DevTools:
- Open DevTools (F12), select the Elements panel.
- Select an element in the DOM tree.
- Open the Accessibility pane in the right sidebar.
- Review the computed accessible name, role, and properties.
- The "Accessibility Tree" view shows the full page as assistive technologies see it.
Firefox DevTools:
- Open DevTools, select the Accessibility panel (may need to enable it first).
- Browse the accessibility tree directly.
- Firefox highlights accessibility issues with color-coded badges.
- Use the "Check for issues" feature to run built-in audits.
What to check:
- Does the element have the correct role? If you set a role, verify it appears in the computed properties.
- Does it have an accessible name? The inspector shows where the name comes from (content, label, aria-label, or aria-labelledby).
- Are states correct? Check that aria-expanded, aria-checked, and aria-selected reflect the actual visual state.
- Are relationships valid? Verify that aria-labelledby, aria-describedby, and aria-controls point to elements that exist and contain the expected content.
- Is the element in the accessibility tree? Elements with aria-hidden="true" or role="presentation" may not appear.
For automated detection of ARIA issues, run axe-core via the browser extension or integrate it into your CI pipeline. It catches invalid roles, missing required attributes, broken ID references, and most of the issues covered in this guide.
Er nettstedet ditt tilgjengelig?
Skann nettstedet ditt gratis og få WCAG-poengsummen din på noen få minutter.
Skann nettstedet ditt gratis