Accessibility Guides for RGAA
Learn how to identify and fix common accessibility issues flagged by Axe Core — so your pages are inclusive and usable for everyone. Also check our HTML Validation Guides.
An image map is an <img> element linked to a <map> element via the usemap attribute. Inside the <map>, individual <area> elements define clickable hotspots that function as links. Each of these <area> elements is essentially a link, and like all links, it must have an accessible name that describes its purpose.
Why This Is an Accessibility Problem
Screen readers cannot interpret graphical content. When a user navigates to a clickable <area> that lacks alternative text, the screen reader has no meaningful label to announce. In many cases, it will fall back to reading the link URL or the image filename — neither of which conveys the purpose of the link. This critically impacts:
- Blind users and deafblind users who rely entirely on screen readers to navigate and interact with content.
- Users with mobility impairments who use assistive technologies like switch devices or voice control, which depend on accessible names to identify interactive elements.
This rule relates to the following WCAG success criteria:
-
WCAG 2.4.4 – Link Purpose (In Context) (Level A): The purpose of each link must be determinable from the link text alone, or from the link text combined with its programmatically determined context. An
<area>without alternative text has no discernible purpose. -
WCAG 4.1.2 – Name, Role, Value (Level A): All user interface components must have a programmatically determined name. An
<area>element is an interactive component, so it requires an accessible name.
This rule also applies under Section 508, EN 301 549, and Trusted Tester requirements.
How to Fix It
Ensure every active <area> element inside a <map> has an accessible name by using one of these methods:
-
altattribute (preferred) — Add descriptivealttext directly to the<area>element. -
aria-labelattribute — Provide a text label viaaria-label. -
aria-labelledbyattribute — Reference another element that contains the label text.
The alt text should clearly describe the purpose or destination of the link, not the visual appearance of the hotspot region.
Also, make sure the parent <img> element has its own alt attribute describing the image as a whole.
Examples
Incorrect: <area> Elements Without Alternative Text
In this example, the <area> elements have no alt text. Screen readers cannot communicate what each clickable region does.
<img src="solar_system.jpg" alt="Solar System" width="472" height="800" usemap="#solar-map">
<map name="solar-map">
<area shape="rect" coords="115,158,276,192" href="https://example.com/mercury">
<area shape="rect" coords="115,193,276,234" href="https://example.com/venus">
<area shape="rect" coords="115,235,276,280" href="https://example.com/earth">
</map>
Correct: <area> Elements With alt Text
Each <area> now has a descriptive alt attribute that communicates the link’s purpose.
<img src="solar_system.jpg" alt="Solar System" width="472" height="800" usemap="#solar-map">
<map name="solar-map">
<area shape="rect" coords="115,158,276,192" href="https://example.com/mercury" alt="Mercury">
<area shape="rect" coords="115,193,276,234" href="https://example.com/venus" alt="Venus">
<area shape="rect" coords="115,235,276,280" href="https://example.com/earth" alt="Earth">
</map>
Correct: Using aria-label Instead of alt
<img src="floor_plan.png" alt="Office floor plan" width="600" height="400" usemap="#office-map">
<map name="office-map">
<area shape="rect" coords="0,0,200,200" href="/rooms/conference-a" aria-label="Conference Room A">
<area shape="rect" coords="200,0,400,200" href="/rooms/kitchen" aria-label="Kitchen">
</map>
Incorrect: Server-Side Image Map
Server-side image maps use the ismap attribute and rely on mouse click coordinates sent to the server. These are not keyboard accessible and provide no text alternatives for individual regions. Avoid this pattern entirely.
<a href="/maps/nav.map">
<img src="navbar.gif" alt="Navigation" ismap>
</a>
Instead, replace server-side image maps with client-side image maps (usemap and <map>) that include proper alt text on each <area>, or use a different navigational pattern altogether such as a standard list of links.
The WAI-ARIA specification organizes roles, states, and properties into a strict taxonomy. Each role defines three categories of attributes it can use:
- Required attributes — must be present for the role to function correctly.
- Supported attributes — optionally enhance the role’s semantics.
- Inherited attributes — come from superclass roles in the ARIA role hierarchy.
Any ARIA attribute that doesn’t fall into one of these categories is not allowed on that role. This applies equally to explicit roles (set with the role attribute) and implicit roles that HTML elements carry by default. For instance, <button> has an implicit role of button, <input type="checkbox"> has an implicit role of checkbox, and <h2> has an implicit role of heading.
When an unsupported attribute appears on an element, the result is unpredictable. A screen reader might silently ignore it, or it might announce contradictory information — for example, describing a heading as a checkable control. In the worst case, invalid role-attribute combinations can break accessibility for entire sections of a page.
Who is affected
This issue has a critical impact on people who use assistive technologies:
- Screen reader users (blind and deafblind users) depend on accurate role and state information to understand and interact with content. Conflicting ARIA attributes can cause elements to be announced as something they are not.
- Voice control users rely on correctly exposed semantics to issue commands targeting specific controls. Misrepresented roles can make controls unreachable by voice.
- Users of switch devices and alternative input methods depend on tools that interpret ARIA roles and attributes to identify operable controls. Invalid attributes can make controls appear inoperable or misrepresent their purpose entirely.
When ARIA attributes conflict with an element’s role, these users may encounter controls that lie about what they do, states that never update correctly, or entire regions that become completely unusable.
Relevant WCAG success criteria
This rule relates to WCAG 2.0, 2.1, and 2.2 Success Criterion 4.1.2: Name, Role, Value (Level A), as well as EN 301 549 clause 9.4.1.2. This criterion requires that all user interface components expose their name, role, and value to assistive technologies in a way that can be programmatically determined. Using unsupported ARIA attributes on a role violates this criterion because it introduces properties that conflict with the element’s actual role, breaking the contract between the page and assistive technology.
How to fix the problem
-
Identify the element’s role. Check for an explicit
roleattribute. If none is present, determine the element’s implicit ARIA role from its HTML tag. For example,<input type="checkbox">has an implicit role ofcheckbox, and<nav>has an implicit role ofnavigation. -
Look up the allowed attributes for that role in the WAI-ARIA specification’s role definitions. Each role page lists its required states and properties, supported states and properties, and inherited properties from superclass roles.
-
Remove or relocate any ARIA attribute that isn’t in the allowed list. If the attribute belongs on a different element within your component, move it there.
-
Reconsider the role. Sometimes the right fix isn’t removing the attribute but changing the element’s role to one that supports the attribute you need. If you want a toggleable control, use
role="switch"orrole="checkbox"instead ofrole="button". -
Consult the ARIA in HTML specification for additional conformance rules about which ARIA attributes are appropriate on specific HTML elements, including restrictions on how elements can be named.
Examples
Incorrect: unsupported attribute on an explicit role
The aria-checked attribute is not supported on role="textbox" because a textbox is not a checkable control. A screen reader might announce this element as both a text input and a checked control.
<div role="textbox" aria-checked="true" contenteditable="true">
Enter your name
</div>
Correct: unsupported attribute removed
Remove aria-checked since it has no meaning on a textbox. Use aria-label to provide an accessible name.
<div role="textbox" contenteditable="true" aria-label="Your name">
</div>
Incorrect: unsupported attribute on an implicit role
The <h2> element has an implicit role of heading. The aria-selected attribute is not supported on headings because headings are not selectable items.
<h2 aria-selected="true">Account Settings</h2>
Correct: unsupported attribute removed from heading
If selection semantics aren’t needed, remove the attribute. If you need selection behavior, use an element with an appropriate role such as tab.
<h2>Account Settings</h2>
Incorrect: role doesn’t match the intended behavior
The developer wants a toggleable control but used role="button", which does not support aria-checked.
<div role="button" aria-checked="true" tabindex="0">
Dark mode
</div>
Correct: role changed to one that supports the attribute
Changing the role to switch makes aria-checked valid. The element remains keyboard-operable via tabindex="0".
<div role="switch" aria-checked="true" tabindex="0" aria-label="Dark mode">
Dark mode
</div>
Incorrect: unsupported attribute on a native HTML element
The <a> element has an implicit role of link. The aria-required attribute is not supported on links because links are not form fields that accept input.
<a href="/terms" aria-required="true">Terms of Service</a>
Correct: unsupported attribute removed from link
Remove aria-required from the link. If you need to indicate that agreeing to terms is mandatory, communicate that through a form control such as a checkbox.
<a href="/terms">Terms of Service</a>
Correct: supported attribute on a matching implicit role
The aria-expanded attribute is supported on the implicit button role, making this combination valid.
<button aria-expanded="false" aria-controls="menu-list">
Menu
</button>
<ul id="menu-list" hidden>
<li><a href="/home">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
When you assign a role like link, button, or menuitem to an element, you are telling the browser and assistive technologies that this element is an interactive command. Screen readers rely on the accessible name of these elements to communicate their purpose to the user. If no accessible name exists, a screen reader might announce something like “button” or “link” with no additional context — leaving the user with no way to understand what the control does.
This issue primarily affects users who are blind or have low vision and rely on screen readers, but it also impacts users with mobility impairments who may use voice control software to activate elements by name. If there is no name, voice control users cannot target the element with a spoken command.
Related WCAG Success Criteria
This rule maps to WCAG Success Criterion 4.1.2: Name, Role, Value (Level A). This criterion requires that all user interface components have a name that can be programmatically determined. It applies across WCAG 2.0, 2.1, and 2.2, and is also referenced in EN 301 549 (9.4.1.2), Trusted Tester guidelines, and RGAA.
The Trusted Tester guidelines further specify that the purpose of each link or button must be determinable from some combination of its text, accessible name, accessible description, or programmatically determined context.
How to Fix It
Ensure that every element with role="link", role="button", or role="menuitem" has an accessible name through one of these methods:
- Inner text content — Place readable text inside the element.
-
aria-labelattribute — Add a non-emptyaria-labelwith a descriptive name. -
aria-labelledbyattribute — Point to theidof another element that contains visible, non-empty text. -
titleattribute — Use atitleattribute as a fallback (thougharia-labelor visible text is preferred).
When possible, prefer using native HTML elements (<a>, <button>) over custom ARIA roles, as they come with built-in accessibility behaviors. If you must use ARIA roles, always make sure the accessible name is clear and describes the action or destination.
Examples
Incorrect: No accessible name
These elements will be flagged because screen readers cannot determine their purpose.
<!-- Empty element with no text or label -->
<div role="link"></div>
<!-- Empty aria-label provides no name -->
<div role="button" aria-label=""></div>
<!-- aria-labelledby points to a non-existent element -->
<div role="menuitem" aria-labelledby="nonexistent"></div>
<!-- aria-labelledby points to an empty element -->
<div role="link" aria-labelledby="empty-label"></div>
<div id="empty-label"></div>
Correct: Accessible name provided
Each of these elements has a discernible name that screen readers can announce.
<!-- Inner text content -->
<div role="link" tabindex="0">Visit our help center</div>
<!-- aria-label attribute -->
<div role="button" tabindex="0" aria-label="Close dialog"></div>
<!-- aria-labelledby pointing to visible text -->
<div role="menuitem" tabindex="0" aria-labelledby="menu-label">
<span id="menu-label">Save document</span>
</div>
<!-- Combination of aria-label and inner text -->
<div role="link" tabindex="0" aria-label="Learn more about pricing">
Learn more
</div>
<!-- title attribute as a fallback -->
<div role="button" tabindex="0" title="Submit form"></div>
Preferred: Use native HTML elements
Native elements handle naming and keyboard behavior automatically, reducing the chance of accessibility issues.
<a href="/help">Visit our help center</a>
<button type="button">Close dialog</button>
Note: When testing with RGAA, issues reported by this rule may need to be mapped to a different RGAA test, such as 6.2.1 for links.
When ARIA attributes are applied to elements where the specification says they should not be used, the result is unpredictable behavior across browsers and assistive technologies. Different browsers handle these conflicts inconsistently — some ignore the ARIA attribute, others override the native state, and still others pass both values through. This inconsistency means that a screen reader user on one browser may hear a completely different state than a screen reader user on another, creating a confusing and unreliable experience.
This rule relates to WCAG 2.0/2.1/2.2 Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that for all user interface components, the name, role, and states can be programmatically determined. When ARIA attributes conflict with native semantics or are used outside their allowed context, the programmatically determined state becomes ambiguous or incorrect. Users affected include people who are blind or deafblind and rely on screen readers, as well as people with mobility disabilities who use alternative input devices that depend on accurate ARIA information.
There are two main scenarios this rule checks:
The aria-checked Attribute on Native Checkboxes
The aria-checked attribute should not be used on an <input type="checkbox"> element. Native HTML checkboxes already communicate their checked state to the browser’s accessibility tree through the checked property. When you add aria-checked on top of this, you create two competing sources of truth. If the native checked state and the aria-checked value fall out of sync — which is easy to do — some assistive technologies will report the native state while others report the ARIA state. The user has no way to know which is correct.
How to fix it
You have two options:
-
Remove
aria-checkedand rely on the nativecheckedattribute or property. If you need a “mixed” or indeterminate state, set theindeterminateproperty on the checkbox via JavaScript. -
Replace the native checkbox with a custom element (e.g., a
<div>or<span>) that usesrole="checkbox"along witharia-checked. When doing this, you must also ensure the element is keyboard accessible (focusable and togglable with Space) and has an accessible name.
Row-Specific Attributes Outside a treegrid
The attributes aria-posinset, aria-setsize, aria-expanded, and aria-level are only valid on a row (a <tr> element or an element with role="row") when that row is inside a treegrid. These attributes describe hierarchical tree relationships — position within a set, nesting level, and expandability — which are concepts that only make sense in a tree grid context. When used inside a plain <table> or grid, these attributes serve no function and may cause screen readers to announce confusing or nonsensical information.
How to fix it
Either remove the unsupported attributes from the rows, or change the parent container’s role to treegrid if the data truly represents a hierarchical, expandable structure. If you switch to treegrid, make sure cells use role="gridcell" and that the keyboard interaction pattern matches what users expect from a tree grid (arrow key navigation for expanding/collapsing rows).
Examples
Bad example: aria-checked on a native checkbox
The aria-checked attribute conflicts with the native checkbox state.
<label>
<input type="checkbox" aria-checked="true">
I agree to make my website accessible
</label>
Good example: native checkbox without aria-checked
The browser communicates the checked state natively — no ARIA override needed.
<label>
<input type="checkbox" checked>
I agree to make my website accessible
</label>
Good example: custom checkbox using aria-checked
When building a custom checkbox, aria-checked is appropriate because there is no native checked state.
<div role="checkbox" aria-checked="true" tabindex="0" aria-label="I agree to make my website accessible">
✓ I agree to make my website accessible
</div>
Bad example: tree attributes on rows inside a plain table
The aria-level and aria-expanded attributes are not valid on rows inside a <table>.
<table>
<tr aria-level="1" aria-expanded="false">
<td>My Downloads</td>
</tr>
<tr aria-level="2">
<td>Documents</td>
</tr>
</table>
Good example: tree attributes on rows inside a treegrid
Changing the table’s role to treegrid makes these attributes valid and meaningful.
<table role="treegrid">
<tr aria-level="1" aria-expanded="false">
<td role="gridcell">My Downloads</td>
</tr>
<tr aria-level="2" class="hidden">
<td role="gridcell">Documents</td>
</tr>
</table>
Good example: removing unsupported attributes from a plain table
If the data is not hierarchical, simply remove the tree-related attributes.
<table>
<tr>
<td>My Downloads</td>
</tr>
<tr>
<td>Documents</td>
</tr>
</table>
The WAI-ARIA specification evolves over time. As it matures, certain roles are deprecated because they were found to be redundant, poorly supported, or better served by other mechanisms. When you use a deprecated role, you’re relying on semantics that assistive technologies are no longer expected to support. This means the role may be completely ignored, misinterpreted, or cause unexpected behavior.
Users most affected include:
- Blind and deafblind users who rely on screen readers to convey the purpose and structure of elements on a page.
- Users with mobility impairments who depend on assistive technologies that use ARIA roles for navigation and interaction.
When a screen reader encounters an element with a deprecated role, it may announce it generically (e.g., as a simple group or section) or skip the role announcement entirely. This strips away meaningful context that helps users understand the content structure and interact with it effectively.
Relevant WCAG Success Criteria
This rule relates to WCAG Success Criterion 4.1.2: Name, Role, Value (Level A). This criterion requires that for all user interface components, the name, role, and value can be programmatically determined and that changes to these are available to assistive technologies. Using a deprecated role means the role cannot be reliably determined, which violates this requirement.
This applies across WCAG 2.0, 2.1, and 2.2 at Level A, as well as EN 301 549 (guideline 9.4.1.2).
How to Fix It
-
Identify deprecated roles in your codebase. The axe-core rule checks all elements with a
roleattribute against the current WAI-ARIA specification. -
Replace deprecated roles with supported alternatives. Currently, the primary deprecated role flagged is:
-
directory: Replace withlist,tree, orsectiondepending on the content structure. If the directory represents a flat list of items (like a table of contents), uselist. If it represents a hierarchical, expandable structure, usetree. If it’s a general grouping of related content, usesection.
-
- Test with assistive technologies after making changes to confirm the new role conveys the intended meaning.
As the WAI-ARIA specification continues to evolve, additional roles may be deprecated in future versions. Keep your ARIA usage up to date by referencing the latest WAI-ARIA specification.
Examples
Incorrect: Using the Deprecated directory Role
<div role="directory">
<ul>
<li><a href="#section-1">Section 1</a></li>
<li><a href="#section-2">Section 2</a></li>
<li><a href="#section-3">Section 3</a></li>
</ul>
</div>
The directory role is deprecated. Assistive technologies may not recognize it, leaving users without useful semantic information about this element.
Correct: Using a Supported Role Instead
If the content is a simple list of links (such as a table of contents), use list or rely on the native <ul> semantics:
<nav aria-label="Table of contents">
<ul>
<li><a href="#section-1">Section 1</a></li>
<li><a href="#section-2">Section 2</a></li>
<li><a href="#section-3">Section 3</a></li>
</ul>
</nav>
In this example, the native <ul> element already provides the list role, and wrapping it in a <nav> with an aria-label gives assistive technology users clear context about its purpose.
Correct: Using tree for Hierarchical Content
If the content represents an expandable, nested structure, use the tree role with appropriate child roles:
<ul role="tree" aria-label="Site map">
<li role="treeitem" aria-expanded="true">
Section 1
<ul role="group">
<li role="treeitem">Subsection 1.1</li>
<li role="treeitem">Subsection 1.2</li>
</ul>
</li>
<li role="treeitem">Section 2</li>
</ul>
Correct: Using section for a General Content Grouping
If the deprecated directory role was used simply to group related content, a <section> element with an accessible name is a good replacement:
<section aria-label="Staff directory">
<ul>
<li>Alex Johnson — Engineering</li>
<li>Maria Garcia — Design</li>
<li>Sam Lee — Marketing</li>
</ul>
</section>
The key is to choose a replacement that accurately reflects the structure and purpose of the content, ensuring assistive technologies can convey it meaningfully to users.
The aria-hidden attribute controls whether an element and its children are exposed to the accessibility tree — the data structure that assistive technologies use to interpret and present web content. When set to "true", the element and all of its descendants are hidden from screen readers and other assistive tools. Applying this attribute to the <body> element is catastrophic because the <body> contains all visible page content, meaning nothing on the page will be announced or navigable for assistive technology users.
This issue has a critical impact on users who are blind or have low vision and rely on screen readers. While sighted users can see and interact with the page normally, screen reader users experience a completely empty page. Keyboard users may still be able to tab to focusable elements like links and buttons, but the screen reader will remain silent — providing no context about what those elements are or what the page contains.
This rule relates to two WCAG 2.0/2.1/2.2 Level A success criteria:
- WCAG 1.3.1 (Info and Relationships): Content structure and relationships must be programmatically determinable. Hiding the entire body breaks this requirement because no structural information reaches assistive technologies.
-
WCAG 4.1.2 (Name, Role, Value): All user interface components must have accessible names and roles. When
aria-hidden="true"is on the<body>, no component can communicate its name, role, or value to assistive technologies.
How to Fix It
The fix is straightforward: remove the aria-hidden="true" attribute from the <body> element.
If aria-hidden="true" was added to the <body> intentionally — for example, as part of a modal dialog pattern — restructure your approach. Instead of hiding the entire body, hide only the background content behind the modal using aria-hidden="true" on sibling wrapper elements, or use the <dialog> element with the inert attribute on background content.
Here are some important things to keep in mind:
-
Never apply
aria-hidden="true"to the<body>element. There is no valid use case for this. -
Use
aria-hidden="true"sparingly and only on non-interactive, decorative, or redundant content that provides no value to screen reader users. -
Be cautious with
aria-hidden="false". It does not reliably re-expose content that is already hidden via CSS (display: none,visibility: hidden) or the HTMLhiddenattribute. Always test thoroughly if you rely on this approach.
Examples
Incorrect: aria-hidden="true" on the body
This makes the entire page inaccessible to assistive technologies:
<body aria-hidden="true">
<header>
<h1>Welcome to My Site</h1>
<nav>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
</header>
<main>
<p>This content is invisible to screen readers.</p>
</main>
</body>
Correct: No aria-hidden on the body
Simply remove the attribute from the <body> element:
<body>
<header>
<h1>Welcome to My Site</h1>
<nav>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
</header>
<main>
<p>This content is now accessible to everyone.</p>
</main>
</body>
Correct: Hiding background content behind a modal dialog
If your original intent was to hide background content while a modal is open, hide the background wrapper — not the <body>:
<body>
<div id="page-wrapper" aria-hidden="true">
<header>
<h1>Welcome to My Site</h1>
</header>
<main>
<p>Main page content hidden while modal is open.</p>
</main>
</div>
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirm Action</h2>
<p>Are you sure you want to proceed?</p>
<button>Yes</button>
<button>Cancel</button>
</div>
</body>
In this pattern, only the #page-wrapper is hidden from assistive technologies, while the dialog remains fully accessible. Remember to remove aria-hidden="true" from the wrapper when the dialog is closed.
The aria-hidden="true" attribute tells assistive technologies to ignore an element and all of its descendants. This is useful for hiding purely decorative content — such as icon fonts or redundant visuals — that would clutter the screen reader experience. However, a serious problem arises when focusable elements like links, buttons, form inputs, or elements with tabindex="0" exist inside an aria-hidden="true" container.
When this happens, keyboard users can still Tab to those elements, but screen readers won’t announce them. The user lands on what feels like an invisible, unlabeled control. They have no way to know what the element is or what it does. This affects blind users, deafblind users, users with low vision who rely on screen readers, and mobility-impaired users who navigate exclusively by keyboard.
It’s also important to understand that aria-hidden="false" on a descendant does not override aria-hidden="true" on an ancestor. Once a parent is hidden from the accessibility tree, all children remain hidden regardless of their own aria-hidden value. Any focusable children inside that subtree still create the same problem.
Related WCAG Success Criteria
This rule primarily relates to WCAG Success Criterion 4.1.2: Name, Role, Value (Level A). This criterion requires that for all user interface components, the name and role can be programmatically determined, and states, properties, and values can be programmatically set. A focusable element inside an aria-hidden="true" container violates this because its name and role are stripped from the accessibility API while it remains reachable via keyboard — making it impossible for assistive technologies to convey its purpose. This rule is flagged across WCAG 2.0, 2.1, and 2.2 at Level A, as well as in Trusted Tester, EN 301 549, and RGAA guidelines.
How to Fix It
There are several strategies to resolve this issue:
-
Remove
aria-hidden="true"from elements that contain focusable children, if those children need to be interactive. -
Remove focusable elements from inside the
aria-hidden="true"container if the entire section is truly meant to be hidden. -
Make focusable elements unfocusable by adding
tabindex="-1"to links, buttons, or other interactive elements inside the hidden container. -
Use the
disabledattribute on form controls (notaria-disabled, which does not actually prevent focus). -
Hide elements with CSS using
display: noneorvisibility: hidden, which removes them from both the accessibility tree and the focus order simultaneously.
If you need to hide content from assistive technologies, ensure equivalent meaning and functionality is still available through other accessible means.
Examples
Incorrect: Focusable link inside aria-hidden="true"
The link is removed from the accessibility tree but still receives keyboard focus.
<div aria-hidden="true">
<a href="/home">Home</a>
</div>
Incorrect: Offscreen focusable link inside aria-hidden="true"
Moving a link offscreen does not remove it from the focus order.
<div aria-hidden="true">
<a href="/" style="position:absolute; top:-999em">Link</a>
</div>
Incorrect: Using aria-disabled instead of disabled
The aria-disabled attribute does not actually prevent the input from receiving focus.
<div aria-hidden="true">
<input aria-disabled="true" />
</div>
Incorrect: Element with tabindex="0" and aria-hidden="true"
Adding tabindex="0" makes a normally non-focusable element focusable, creating a conflict with aria-hidden="true".
<p tabindex="0" aria-hidden="true">Some descriptive text</p>
Incorrect: Trying to override aria-hidden on a descendant
Setting aria-hidden="false" on a child does not re-expose it when a parent has aria-hidden="true". The button remains hidden from assistive technologies but still receives focus.
<div aria-hidden="true">
<div aria-hidden="false">
<button>Submit</button>
</div>
</div>
Incorrect: Focusable <summary> inside aria-hidden="true"
The <summary> element is natively focusable.
<details aria-hidden="true">
<summary>More info</summary>
<p>Additional details here.</p>
</details>
Correct: Non-focusable content inside aria-hidden="true"
A paragraph with no interactive elements is safe to hide.
<p aria-hidden="true">Decorative description text</p>
Correct: Focusable elements hidden with CSS display: none
Using display: none removes the link from both the focus order and the accessibility tree.
<div aria-hidden="true">
<a href="/" style="display:none">Link</a>
</div>
Correct: Focusable elements made unfocusable with tabindex="-1"
Adding tabindex="-1" removes the button from the tab order.
<div aria-hidden="true">
<button tabindex="-1">Close</button>
</div>
Correct: Form input properly disabled with the disabled attribute
The disabled attribute prevents the input from receiving focus entirely.
<input disabled aria-hidden="true" />
Correct: Removing aria-hidden and keeping elements interactive
If the content needs to be focusable, simply don’t hide it from assistive technologies.
<div>
<a href="/home">Home</a>
</div>
When you use ARIA roles to create custom input controls instead of native HTML elements, the browser no longer automatically associates a label with the control. Native <input>, <select>, and <textarea> elements support the <label> element through implicit wrapping or explicit for/id pairing, but custom <div> or <span> elements with ARIA roles do not. This means you must supply an accessible name through ARIA-specific mechanisms.
This rule applies to elements with any of the following ARIA input roles:
-
combobox -
listbox -
searchbox -
slider -
spinbutton -
textbox
Who is affected
Without an accessible name, users who rely on screen readers (including blind and deafblind users) hear only the role of the element — for example, “textbox” — with no indication of what information to enter. Users with mobility impairments who navigate via voice control also depend on accessible names to target specific controls by speaking their labels. This creates a serious barrier to interaction.
Related WCAG success criteria
This rule maps to WCAG 2.0, 2.1, and 2.2 Success Criterion 4.1.2: Name, Role, Value (Level A). This criterion requires that all user interface components have a name that can be programmatically determined by assistive technologies. It is a Level A requirement, meaning it represents the minimum baseline of accessibility.
How to fix the problem
Because <label> elements do not work reliably with non-native elements like <div> or <span>, you need to use one of these approaches:
-
aria-label— Add a concise, descriptive label directly on the element. -
aria-labelledby— Point to theidof one or more visible elements that serve as the label. -
title— Use thetitleattribute as a last resort (it is less discoverable for sighted users).
Make sure the accessible name you provide is not empty or whitespace-only, and that any id referenced by aria-labelledby actually exists in the DOM.
Common mistakes
-
Setting
aria-labelto an empty or whitespace-only string (e.g.,aria-label=" "). -
Using
aria-labelledbywith anidthat doesn’t exist on the page. -
Wrapping a custom ARIA widget in a
<label>element, which does not provide a programmatic name for non-native elements in most browser/screen reader combinations. -
Using
<label for="id">to point to a<div>— theforattribute only works with native labelable elements like<input>,<select>, and<textarea>.
Examples
Incorrect: no accessible name on a combobox
The <div> has a combobox role but no label of any kind.
<div role="combobox">England</div>
Incorrect: aria-label is only whitespace
An empty or whitespace-only aria-label does not count as an accessible name.
<div aria-label=" " role="combobox">England</div>
Incorrect: aria-labelledby references a non-existent id
The referenced id must exist in the document; otherwise the accessible name resolves to empty.
<div role="listbox" aria-labelledby="color-label">
<div role="option">Orange</div>
</div>
<!-- No element with id="color-label" exists -->
Incorrect: <label> wrapping a non-native element
Implicit label association does not work for <div> elements with ARIA roles.
<label>
First name
<div role="textbox"></div>
</label>
Incorrect: explicit <label for> targeting a non-native element
The for attribute only creates a programmatic association with native labelable elements.
<label for="name-field">First name</label>
<div role="textbox" id="name-field"></div>
Correct: aria-label on a combobox
<div role="combobox" aria-label="Country">England</div>
Correct: aria-labelledby pointing to a visible label
<p id="color-label">Select a color:</p>
<div role="listbox" aria-labelledby="color-label">
<div role="option">Orange</div>
</div>
Correct: aria-labelledby on a searchbox
<p id="search-label">Search currency pairs:</p>
<div role="searchbox"
contenteditable="true"
aria-labelledby="search-label"></div>
Correct: aria-label on a slider
<div role="slider"
aria-label="Choose a value"
aria-valuemin="1"
aria-valuemax="7"
aria-valuenow="2"
tabindex="0"></div>
Correct: aria-label on a spinbutton
<div role="spinbutton"
aria-label="Enter quantity"
aria-valuemin="0"
aria-valuemax="10"
aria-valuenow="8"
tabindex="0"></div>
Correct: aria-labelledby on a textbox
<p id="name-label">First name</p>
<div role="textbox"
contenteditable="true"
aria-labelledby="name-label"></div>
When possible, prefer native HTML form controls (<input>, <select>, <textarea>) because they have built-in label support and keyboard behavior. Use custom ARIA input roles only when native elements cannot meet your design requirements, and always ensure those custom controls have an accessible name.
The meter role in ARIA represents a scalar measurement within a known range — think of a gauge showing a value like CPU temperature, password strength, or storage capacity. When a screen reader encounters an element with role="meter", it announces the element as a meter, but without an accessible name, it cannot convey what is being measured. The user hears something like “meter” with no context, which is effectively meaningless.
This issue primarily affects users who are blind or have low vision and rely on screen readers, as well as users with mobility impairments who may navigate via assistive technologies. It relates to WCAG 2.0/2.1/2.2 Success Criterion 1.1.1: Non-text Content (Level A), which requires that all non-text content has a text alternative that serves an equivalent purpose. A meter without a name fails to provide this text alternative.
How to Fix
Ensure every element with role="meter" has a clear, descriptive accessible name using one of these methods:
-
aria-label— Add descriptive text directly to the element. -
aria-labelledby— Reference another visible element that contains the label text. The referenced element must exist and contain non-empty text. -
title— Use thetitleattribute as a fallback naming method (thougharia-labeloraria-labelledbyare generally preferred).
The name should clearly describe what the meter is measuring so users understand its purpose in context.
Examples
Incorrect: Meter with no accessible name
The following meter has no name at all. A screen reader will announce it as a meter but cannot tell the user what it measures.
<div role="meter" aria-valuemin="0" aria-valuemax="100" aria-valuenow="75"></div>
Incorrect: Empty aria-label
An empty aria-label is equivalent to having no name.
<div role="meter" aria-label="" aria-valuemin="0" aria-valuemax="100" aria-valuenow="75"></div>
Incorrect: aria-labelledby referencing a nonexistent or empty element
If the referenced element doesn’t exist or has no text content, the meter still lacks an accessible name.
<div role="meter" aria-labelledby="nonexistent" aria-valuemin="0" aria-valuemax="100" aria-valuenow="75"></div>
<div role="meter" aria-labelledby="empty-label" aria-valuemin="0" aria-valuemax="100" aria-valuenow="75"></div>
<div id="empty-label"></div>
Correct: Using aria-label
<div role="meter" aria-label="Disk usage" aria-valuemin="0" aria-valuemax="100" aria-valuenow="75"></div>
Correct: Using aria-labelledby
<span id="meter-label">Battery level</span>
<div role="meter" aria-labelledby="meter-label" aria-valuemin="0" aria-valuemax="100" aria-valuenow="42"></div>
Correct: Using the title attribute
<div role="meter" title="Signal strength" aria-valuemin="0" aria-valuemax="5" aria-valuenow="3"></div>
Correct: Using the native <meter> element with a <label>
When possible, prefer the native HTML <meter> element, which has built-in semantics and can be associated with a <label>.
<label for="fuel">Fuel level</label>
<meter id="fuel" min="0" max="100" value="68">68%</meter>
When a progress bar lacks an accessible name, users who rely on assistive technologies have no way to distinguish it from other progress bars on the page or understand its purpose. Imagine a page with two progress bars—one for a file upload and one for a software installation. Without accessible names, a screen reader user would hear “progress bar” twice with no context for either. This issue primarily affects users who are blind or have low vision, as well as users with mobility impairments who may navigate using voice commands that reference element names.
This rule relates to WCAG Success Criterion 1.1.1: Non-text Content (Level A), which requires that all non-text content has a text alternative that serves the equivalent purpose. A progress bar is a non-text indicator of status, so it needs a text-based name to convey its meaning to assistive technology users. This criterion applies across WCAG 2.0, 2.1, and 2.2 at Level A, meaning it is a baseline requirement.
How to Fix
Ensure every element with role="progressbar" has a meaningful accessible name using one of these techniques:
-
aria-label— Provide a concise, descriptive label directly on the element. -
aria-labelledby— Reference theidof another element that contains visible label text. -
title— Use thetitleattribute as a fallback (thougharia-labeloraria-labelledbyare preferred).
The name should clearly describe what process or task the progress bar represents, such as “File upload progress” or “Installation progress.”
Examples
Failing: Progress bar with no accessible name
A progress bar with no labeling attributes is announced generically with no context.
<div role="progressbar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100">
</div>
Failing: Empty aria-label
An empty aria-label provides no name, so the progress bar remains unlabeled.
<div role="progressbar" aria-label="" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100">
</div>
Failing: aria-labelledby pointing to a nonexistent or empty element
If the referenced element doesn’t exist or has no text content, the progress bar still has no accessible name.
<div role="progressbar" aria-labelledby="missing-id" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100">
</div>
<!-- Or referencing an empty element -->
<div role="progressbar" aria-labelledby="empty-label" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100">
</div>
<div id="empty-label"></div>
Passing: Using aria-label
<div role="progressbar" aria-label="File upload progress" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100">
</div>
A screen reader will announce something like: “File upload progress, progress bar, 50%.”
Passing: Using aria-labelledby
<h3 id="upload-heading">Uploading resume.pdf</h3>
<div role="progressbar" aria-labelledby="upload-heading" aria-valuenow="70" aria-valuemin="0" aria-valuemax="100">
</div>
This approach is especially useful when a visible heading or label already describes the progress bar, keeping the visual and accessible names in sync.
Passing: Using title
<div role="progressbar" title="Installation progress" aria-valuenow="90" aria-valuemin="0" aria-valuemax="100">
</div>
The title attribute works as a naming mechanism, but note that it may also produce a tooltip on hover. Prefer aria-label or aria-labelledby when possible for a more consistent experience across assistive technologies.
The WAI-ARIA specification defines which attributes are permitted, required, or prohibited for each role. When you use a prohibited attribute on a role, you’re relying on a combination that assistive technologies are not designed to support. The result is that the information you intended to communicate — such as a label or description — may be silently dropped.
This creates a serious problem for users who depend on assistive technologies, including people who are blind, deafblind, or have mobility impairments. Screen readers might ignore the prohibited attribute, leaving the user without context. Some assistive technologies may attempt to compensate, leading to inconsistent or confusing behavior across different tools and browsers.
A common example is using aria-label on an element with role="presentation" or role="none". These roles explicitly tell assistive technologies to ignore the element’s semantics, so labeling it with aria-label is contradictory. Similarly, text-level semantic roles like code, insertion, deletion, strong, emphasis, subscript, superscript, and paragraph prohibit aria-label and aria-labelledby because these roles represent inline text content that should not carry an accessible name separate from their text content.
Related WCAG success criteria
This rule relates to WCAG Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that for all user interface components, the name, role, and states/properties can be programmatically determined. Using prohibited ARIA attributes violates this criterion because the intended name or state information cannot be reliably communicated to assistive technologies.
How to fix it
When axe flags a prohibited ARIA attribute, consider these approaches:
- Remove the prohibited attribute — If the information it conveys is not essential, simply remove it.
-
Change the element’s role — Switch to a role that permits the attribute you need. For instance, if you need
aria-label, don’t userole="none". - Provide the information as visible text — Instead of relying on the prohibited attribute, include the information directly in the page content where all users can access it.
- Move the attribute to a different element — Place the attribute on a parent or child element that has a role supporting it.
Examples
Incorrect: aria-label on an element with role="presentation"
The presentation role tells assistive technologies to ignore the element. Adding aria-label contradicts this and will be ignored.
<div role="presentation" aria-label="Navigation section">
<a href="/home">Home</a>
<a href="/about">About</a>
</div>
Correct: Use a role that supports aria-label
If the element needs an accessible name, use a role that permits labeling, such as navigation.
<nav aria-label="Navigation section">
<a href="/home">Home</a>
<a href="/about">About</a>
</nav>
Incorrect: aria-label on a text-level semantic role
Roles like code, strong, emphasis, insertion, and deletion prohibit aria-label and aria-labelledby.
<span role="code" aria-label="JavaScript variable declaration">
const x = 10;
</span>
Correct: Use visible text instead
Provide context through surrounding text content rather than a prohibited attribute.
<p>
The following JavaScript variable declaration
<code>const x = 10;</code>
assigns the value 10 to x.
</p>
Incorrect: aria-labelledby on role="none"
<h2 id="section-title">Features</h2>
<table role="none" aria-labelledby="section-title">
<tr>
<td>Fast</td>
<td>Reliable</td>
</tr>
</table>
Correct: Remove the conflicting role or the prohibited attribute
If the table needs to be labeled, remove role="none" so it retains its native table semantics.
<h2 id="section-title">Features</h2>
<table aria-labelledby="section-title">
<tr>
<td>Fast</td>
<td>Reliable</td>
</tr>
</table>
If the table is truly presentational and doesn’t need a label, remove the aria-labelledby attribute instead.
<table role="none">
<tr>
<td>Fast</td>
<td>Reliable</td>
</tr>
</table>
Why This Is an Accessibility Problem
ARIA roles describe what an element is and how it behaves. Many roles depend on specific state or property attributes to convey critical information about the element. For example, a checkbox role requires aria-checked so users know whether the checkbox is selected. Without it, a screen reader user hears “checkbox” but has no idea whether it’s checked or unchecked.
This issue affects users who are blind, deafblind, or have mobility impairments and rely on assistive technologies to interact with web content. When required attributes are missing, these users lose essential context about the state of interactive widgets.
This rule relates to WCAG 2.0, 2.1, and 2.2 Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that for all user interface components, the name, role, and states can be programmatically determined. Missing required ARIA attributes directly violate this criterion because the element’s state cannot be communicated to assistive technologies.
How Required Attributes Work
Each ARIA role has a set of required states and properties defined in the WAI-ARIA specification. These attributes are essential for the role to function correctly. Some common examples:
| Role | Required Attribute(s) |
|---|---|
checkbox |
aria-checked |
combobox |
aria-expanded, aria-controls |
slider |
aria-valuenow, aria-valuemin, aria-valuemax (note: aria-valuemin and aria-valuemax have implicit defaults of 0 and 100) |
option |
aria-selected |
scrollbar |
aria-controls, aria-valuenow |
separator (focusable) |
aria-valuenow |
meter |
aria-valuenow |
heading |
aria-level |
Some roles inherit requirements from ancestor roles. When a role inherits from multiple ancestors and one ancestor marks a property as supported while another marks it as required, the property becomes required on the inheriting role.
In some cases, default values defined in the specification satisfy the requirement automatically, so you may not always need to explicitly set every attribute. However, explicitly providing required attributes is the safest approach.
How to Fix the Problem
-
Identify the ARIA
roleon the element. - Look up the role in the WAI-ARIA Roles documentation to find its required states and properties.
- Add any missing required attributes with appropriate values.
-
Ensure the attribute values are updated dynamically as the widget state changes (e.g., toggling
aria-checkedbetweentrueandfalsewhen a checkbox is clicked).
Alternatively, consider whether a native HTML element could replace the custom ARIA widget. Native elements like <input type="checkbox">, <input type="range">, and <select> handle state management automatically without needing ARIA attributes.
Examples
Incorrect: Checkbox missing aria-checked
<div role="checkbox" tabindex="0">
Accept terms and conditions
</div>
A screen reader announces this as a checkbox, but the user has no way to know if it is checked or unchecked.
Correct: Checkbox with aria-checked
<div role="checkbox" tabindex="0" aria-checked="false">
Accept terms and conditions
</div>
Better: Use a native HTML checkbox
<label>
<input type="checkbox">
Accept terms and conditions
</label>
Incorrect: Slider missing required value attributes
<div role="slider" tabindex="0">
Volume
</div>
Without aria-valuenow, assistive technologies cannot report the current value of the slider.
Correct: Slider with required attributes
<div role="slider" tabindex="0"
aria-valuenow="50"
aria-valuemin="0"
aria-valuemax="100"
aria-label="Volume">
</div>
Incorrect: Combobox missing aria-expanded and aria-controls
<input role="combobox" type="text" aria-label="Search">
Correct: Combobox with required attributes
<input role="combobox" type="text"
aria-label="Search"
aria-expanded="false"
aria-controls="search-listbox">
<ul id="search-listbox" role="listbox" hidden>
<li role="option">Option 1</li>
<li role="option">Option 2</li>
</ul>
Incorrect: Heading missing aria-level
<div role="heading">Section Title</div>
Correct: Heading with aria-level
<div role="heading" aria-level="2">Section Title</div>
Better: Use a native heading element
<h2>Section Title</h2>
The WAI-ARIA specification defines a strict hierarchy for many roles, where parent and child relationships must be maintained for the accessibility semantics to work as intended. For example, an element with role="list" must contain elements with role="listitem", just as a role="menu" must contain role="menuitem" (or related menu item roles). When these required child roles are absent, assistive technologies like screen readers cannot properly convey the structure of the component. A screen reader user navigating a tree view, for instance, needs to understand the parent container, individual items, and sibling relationships — none of which are communicated correctly if the expected child roles are missing.
This issue critically affects users who are blind, deafblind, or have mobility impairments and rely on assistive technologies to navigate and interact with complex widgets. Without the correct role hierarchy, these users may not understand what type of content they’re interacting with, how many items exist, or how to navigate between them.
This rule relates to WCAG 2.0/2.1/2.2 Success Criterion 1.3.1: Info and Relationships (Level A), which requires that information, structure, and relationships conveyed through presentation can be programmatically determined. When ARIA roles lack their required children, the structural relationships are broken and cannot be programmatically determined by assistive technologies.
There are two ways the required parent-child relationship can be established:
- DOM structure: The child elements with the required roles are direct descendants (or appropriate descendants) of the parent element in the DOM.
-
aria-owns: When the DOM hierarchy doesn’t match the intended accessibility tree structure, thearia-ownsattribute can be used on the parent to explicitly associate child elements that exist elsewhere in the DOM.
How to Fix
- Identify the ARIA role on the parent element.
- Consult the WAI-ARIA specification to determine which child roles are required for that parent role.
-
Ensure all direct children (or owned children via
aria-owns) have the correct required roles. - If you cannot add the required child roles, consider whether the parent role is appropriate for your use case, or use native HTML elements that provide these semantics automatically.
Common Parent-Child Role Requirements
| Parent Role | Required Child Role(s) |
|---|---|
list |
listitem |
menu |
menuitem, menuitemcheckbox, or menuitemradio |
menubar |
menuitem, menuitemcheckbox, or menuitemradio |
tablist |
tab |
tree |
treeitem or group |
grid |
row or rowgroup |
table |
row or rowgroup |
row |
cell, columnheader, gridcell, or rowheader |
feed |
article |
Examples
Incorrect: Missing required child roles
This tablist has children that lack the required tab role:
<div role="tablist">
<div>Tab 1</div>
<div>Tab 2</div>
<div>Tab 3</div>
</div>
This list contains <div> elements without the listitem role:
<div role="list">
<div>Apple</div>
<div>Banana</div>
<div>Cherry</div>
</div>
This menu has children with incorrect roles:
<div role="menu">
<div role="option">Cut</div>
<div role="option">Copy</div>
<div role="option">Paste</div>
</div>
Correct: Required child roles present
The tablist now contains children with role="tab":
<div role="tablist">
<div role="tab" aria-selected="true">Tab 1</div>
<div role="tab">Tab 2</div>
<div role="tab">Tab 3</div>
</div>
The list contains children with role="listitem":
<div role="list">
<div role="listitem">Apple</div>
<div role="listitem">Banana</div>
<div role="listitem">Cherry</div>
</div>
The menu contains children with role="menuitem":
<div role="menu">
<div role="menuitem">Cut</div>
<div role="menuitem">Copy</div>
<div role="menuitem">Paste</div>
</div>
Correct: Using aria-owns for children outside the DOM hierarchy
When required children are not direct descendants in the DOM, use aria-owns to establish the relationship:
<div role="tablist" aria-owns="tab1 tab2 tab3">
<!-- Tabs may be rendered elsewhere in the DOM -->
</div>
<div role="tab" id="tab1" aria-selected="true">Tab 1</div>
<div role="tab" id="tab2">Tab 2</div>
<div role="tab" id="tab3">Tab 3</div>
Correct: Using native HTML elements instead
Native HTML elements automatically provide the correct role relationships without any ARIA attributes:
<ul>
<li>Apple</li>
<li>Banana</li>
<li>Cherry</li>
</ul>
Whenever possible, prefer native HTML elements over ARIA roles. Native elements come with built-in semantics, keyboard behavior, and accessibility support that don’t require manual role management.
The WAI-ARIA specification defines a strict hierarchy for many roles. Some roles are only meaningful when they appear as children of particular parent roles. For example, a tab must be owned by a tablist, a listitem must be owned by a list or group, and a menuitem must be owned by a menu or menubar. When these parent-child relationships are missing, the role becomes semantically orphaned — assistive technologies like screen readers cannot communicate the element’s context, position, or purpose to the user.
Why This Matters
This rule relates to WCAG 2.0/2.1/2.2 Success Criterion 1.3.1: Info and Relationships (Level A), which requires that information, structure, and relationships conveyed visually are also available programmatically. When an ARIA role lacks its required parent, the structural relationship is lost in the accessibility tree.
Users most affected include:
- Blind and deafblind users who rely on screen readers. Without the correct parent role, screen readers cannot announce context such as “item 3 of 5 in a list” or “tab 2 of 4.”
- Users with mobility impairments who depend on assistive technology for navigation. Missing parent roles can break expected keyboard interaction patterns for composite widgets like menus, tablists, and trees.
The user impact is critical — an orphaned role can make an entire widget unusable for assistive technology users.
Common Required Parent Roles
Here are some frequently used roles and their required parents:
| Child Role | Required Parent Role(s) |
|---|---|
listitem |
list or group |
tab |
tablist |
tabpanel |
(associated via tablist pattern) |
menuitem, menuitemcheckbox, menuitemradio |
menu, menubar, or group |
treeitem |
tree or group |
row |
table, grid, rowgroup, or treegrid |
cell, gridcell |
row |
columnheader, rowheader |
row |
option |
listbox or group |
How to Fix the Problem
-
Wrap the element in its required parent role. The simplest fix is to ensure the DOM structure reflects the required hierarchy. Place the element inside a container that has the correct parent role.
-
Use
aria-ownswhen DOM structure doesn’t match. If the child element cannot be a DOM descendant of the parent (e.g., due to layout constraints), you can usearia-ownson the parent element to establish the relationship programmatically. Thearia-ownsattribute tells assistive technologies that the referenced element should be treated as a child, regardless of DOM position. -
Check the WAI-ARIA specification for the specific role you’re using to confirm which parent roles are required.
Examples
Incorrect: listitem Without a list Parent
The listitem role requires a parent with role="list" or role="group", but here it sits directly inside a plain div:
<div>
<div role="listitem">Apples</div>
<div role="listitem">Bananas</div>
<div role="listitem">Cherries</div>
</div>
Correct: listitem Inside a list Parent
<div role="list">
<div role="listitem">Apples</div>
<div role="listitem">Bananas</div>
<div role="listitem">Cherries</div>
</div>
Incorrect: tab Without a tablist Parent
<div>
<button role="tab" aria-selected="true">Tab 1</button>
<button role="tab" aria-selected="false">Tab 2</button>
</div>
Correct: tab Inside a tablist Parent
<div role="tablist">
<button role="tab" aria-selected="true">Tab 1</button>
<button role="tab" aria-selected="false">Tab 2</button>
</div>
Correct: Using aria-owns When DOM Structure Differs
When layout constraints prevent nesting the child inside the parent in the DOM, use aria-owns to establish the relationship:
<div role="tablist" aria-owns="tab1 tab2">
<!-- Tabs are elsewhere in the DOM due to layout needs -->
</div>
<div id="tab1" role="tab" aria-selected="true">Tab 1</div>
<div id="tab2" role="tab" aria-selected="false">Tab 2</div>
Incorrect: menuitem Outside a menu
<div>
<button role="menuitem">Cut</button>
<button role="menuitem">Copy</button>
<button role="menuitem">Paste</button>
</div>
Correct: menuitem Inside a menu
<div role="menu">
<button role="menuitem">Cut</button>
<button role="menuitem">Copy</button>
<button role="menuitem">Paste</button>
</div>
When you assign a role attribute to an HTML element, you are explicitly telling browsers and assistive technologies what that element represents and how users should interact with it. If the value is misspelled, invented, or references an abstract role (like widget, roletype, landmark, or structure), the browser cannot map the element to any known role. The result is that assistive technologies either ignore the role entirely or fall back to the element’s native semantics, which may not reflect your intent.
This is a critical accessibility problem. Users who rely on screen readers — including people who are blind, deafblind, or have mobility impairments — depend on accurate role information to navigate and interact with a page. A button announced as a generic element, or a navigation landmark that isn’t recognized at all, can make an interface confusing or completely unusable.
This rule relates to WCAG Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that the role of every user interface component can be programmatically determined. It applies across WCAG 2.0, 2.1, and 2.2, as well as EN 301 549 (Section 9.4.1.2).
How to Fix
-
Check spelling. The most common cause of invalid roles is a simple typo, such as
role="buton"instead ofrole="button". - Use only defined roles. Every value must correspond to an actual role in the WAI-ARIA specification.
-
Never use abstract roles. Abstract roles like
widget,roletype,landmark,structure,input,range,section,sectionhead,select,command, andcompositeexist only as conceptual categories in the spec. They must not be used in markup. -
Consider whether you need
roleat all. Native HTML elements like<button>,<nav>, and<table>already carry implicit roles. Use native elements whenever possible instead of addingroleattributes to generic<div>or<span>elements.
Valid Roles by Category
Here is a summary of valid, non-abstract ARIA roles you can use:
-
Document structure:
application,article,blockquote,caption,code,definition,deletion,document,emphasis,feed,figure,generic,group,heading,img,insertion,list,listitem,mark,math,meter,none,note,paragraph,presentation,separator(when not focusable),strong,subscript,superscript,term,time,toolbar,tooltip -
Landmark:
banner,complementary,contentinfo,form,main,navigation,region,search -
Live region:
alert,log,marquee,status,timer -
Widget:
button,checkbox,gridcell,link,menuitem,menuitemcheckbox,menuitemradio,option,progressbar,radio,scrollbar,searchbox,separator(when focusable),slider,spinbutton,switch,tab,tabpanel,textbox,treeitem -
Composite widget:
combobox,grid,listbox,menu,menubar,radiogroup,tablist,tree,treegrid -
Table:
cell,columnheader,row,rowgroup,rowheader,table -
Window:
alertdialog,dialog
Examples
Incorrect: Misspelled Role
A typo in the role value means assistive technologies won’t recognize it.
<div role="nagivation">
<a href="/">Home</a>
<a href="/about">About</a>
</div>
Incorrect: Made-Up Role
Using a value that doesn’t exist in the ARIA specification.
<div role="footer-container">
<p>© 2024 Example Corp</p>
</div>
Incorrect: Abstract Role
Abstract roles are not allowed in markup.
<div role="widget">
<button>Save</button>
</div>
Correct: Valid ARIA Role
<div role="navigation" aria-label="Main">
<a href="/">Home</a>
<a href="/about">About</a>
</div>
Correct: Using Native HTML Instead of a Role
When a native element already carries the semantics you need, prefer it over a role attribute.
<nav aria-label="Main">
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
Correct: Valid Widget Role
<div role="tablist" aria-label="Settings">
<button role="tab" aria-selected="true" aria-controls="panel-1">General</button>
<button role="tab" aria-selected="false" aria-controls="panel-2">Advanced</button>
</div>
<div role="tabpanel" id="panel-1">General settings content</div>
<div role="tabpanel" id="panel-2" hidden>Advanced settings content</div>
Toggle fields are interactive controls that let users select options, toggle settings, or make choices. When these controls are built using ARIA roles instead of native HTML elements, the browser does not automatically derive a name from a <label> element or inner content the way it would for a native <input type="checkbox">. You must explicitly provide an accessible name so assistive technologies can announce the control’s purpose.
Without an accessible name, a screen reader might announce something like “checkbox, unchecked” with no indication of what the checkbox represents. This makes the interface unusable for people who are blind, deafblind, or who rely on voice control software. Users with mobility impairments who use speech recognition tools also depend on accessible names to target and activate controls by voice.
This rule applies to elements with the following ARIA roles:
-
checkbox -
menuitemcheckbox -
menuitemradio -
radio -
radiogroup -
switch
Related WCAG Success Criteria
This rule maps to WCAG 2.0, 2.1, and 2.2 Success Criterion 4.1.2: Name, Role, Value (Level A). This criterion requires that for all user interface components, the name and role can be programmatically determined. Toggle fields that lack an accessible name violate this requirement because assistive technologies cannot convey the control’s purpose to the user.
How to Fix It
You can provide an accessible name for ARIA toggle fields using any of these techniques:
- Inner text content — Place descriptive text inside the element. The browser uses this text as the accessible name.
-
aria-label— Add anaria-labelattribute with a descriptive string directly on the element. -
aria-labelledby— Reference theidof another element that contains the label text. Ensure the referenced element actually exists in the DOM. -
title— Use thetitleattribute as a last resort. While it does provide an accessible name, it is less discoverable for sighted users and not consistently exposed by all assistive technologies.
Important: If you use aria-labelledby, make sure the referenced id matches an element that exists and contains meaningful text. Pointing to a non-existent id results in no accessible name.
Examples
Incorrect: Toggle fields without accessible names
<!-- checkbox with a broken aria-labelledby reference -->
<div role="checkbox" aria-checked="false" aria-labelledby="nonexistent-id"></div>
<!-- menuitemcheckbox with no name -->
<ul role="menu">
<li role="menuitemcheckbox" aria-checked="true"></li>
</ul>
<!-- menuitemradio with no name -->
<ul role="menu">
<li role="menuitemradio" aria-checked="false"></li>
</ul>
<!-- radio with no name -->
<div role="radiogroup">
<div role="radio" aria-checked="false" tabindex="0"></div>
</div>
<!-- switch with no name and empty child spans -->
<div role="switch" aria-checked="true">
<span></span>
<span></span>
</div>
Each of these elements has no text content, no valid aria-label, no working aria-labelledby, and no title. A screen reader cannot announce what they represent.
Correct: Toggle fields with accessible names
<!-- checkbox: name from inner text content -->
<div role="checkbox" aria-checked="false" tabindex="0">
Subscribe to newsletter
</div>
<!-- menuitemcheckbox: name from aria-label -->
<ul role="menu">
<li role="menuitemcheckbox"
aria-checked="true"
aria-label="Word wrap">
</li>
</ul>
<!-- menuitemradio: name from aria-labelledby -->
<p id="font-label">Sans-serif</p>
<ul role="menu">
<li role="menuitemradio"
aria-checked="true"
aria-labelledby="font-label">
</li>
</ul>
<!-- radio: name from title -->
<div role="radiogroup" aria-label="Crust type">
<div role="radio"
aria-checked="false"
tabindex="0"
title="Regular Crust">
</div>
</div>
<!-- switch: name from aria-label -->
<div role="switch"
aria-checked="true"
aria-label="Dark mode"
tabindex="0">
<span>off</span>
<span>on</span>
</div>
Preferred: Use native HTML elements when possible
Native HTML elements come with built-in accessible name mechanisms and keyboard behavior. Whenever possible, prefer them over custom ARIA toggle fields:
<!-- Native checkbox with a label -->
<label>
<input type="checkbox"> Subscribe to newsletter
</label>
<!-- Native radio buttons with a fieldset -->
<fieldset>
<legend>Crust type</legend>
<label><input type="radio" name="crust" value="regular"> Regular Crust</label>
<label><input type="radio" name="crust" value="thin"> Thin Crust</label>
</fieldset>
Using native elements reduces the risk of accessibility issues and eliminates the need to manually manage roles, states, and keyboard interactions.
ARIA attributes communicate essential information about the state, properties, and roles of interface elements to assistive technologies like screen readers and braille displays. When these attributes contain invalid values, the communication breaks down entirely. A screen reader might ignore the attribute, misrepresent the element’s state, or behave unpredictably — any of which can make content unusable.
This issue has a critical impact on users who are blind, deafblind, or have mobility impairments who rely on assistive technology to navigate and interact with web content. For example, if a checkbox uses aria-checked="ture" instead of aria-checked="true", a screen reader cannot determine whether the checkbox is checked, leaving the user unable to understand the form’s current state.
This rule maps to WCAG 2.0, 2.1, and 2.2 Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that for all user interface components, the name, role, and value can be programmatically determined and set by assistive technologies. Invalid ARIA values violate this criterion because the value cannot be meaningfully determined.
How to Fix It
For every aria- attribute in your markup, confirm that its value:
-
Is spelled correctly — a typo like
"flase"instead of"false"will cause a failure. - Is a permitted value for that specific attribute — each ARIA attribute accepts only certain value types.
- Makes sense in context — the value must be meaningful for the role and state of the element.
Understanding Value Types
Different ARIA attributes accept different types of values. Here are the most common:
-
true/false— Boolean values. Default is typically"false". Example:aria-hidden="true". -
tristate— Accepts"true","false", or"mixed". Example:aria-checked="mixed"for a partially selected checkbox. -
true/false/undefined— Liketrue/false, but"undefined"explicitly indicates the property is not relevant. -
token— One value from a limited set of allowed strings. Example:aria-sortaccepts"ascending","descending","none", or"other". -
token list— A space-separated list of one or more allowed tokens. Example:aria-relevant="additions text". -
ID reference— Theidof another element in the same document. Example:aria-labelledby="heading-1". -
ID reference list— A space-separated list of element IDs. Example:aria-describedby="desc1 desc2". -
integer— A whole number with no fractional part. Example:aria-level="2". -
number— Any real number. Example:aria-valuenow="3.5". -
string— An unconstrained text value. Example:aria-label="Close dialog".
For a complete reference of which values each attribute accepts, consult the WAI-ARIA 1.1 Supported States and Properties.
Watch Out for Common Pitfalls
-
Typos in boolean values —
"ture","flase","yes", and"no"are all invalid for attributes that expect"true"or"false". -
Using wrong tokens — Attributes like
aria-sort,aria-autocomplete, andaria-currentonly accept specific string values. -
Referencing non-existent IDs — If
aria-labelledbypoints to anidthat doesn’t exist in the document, the reference is invalid. -
Implicit defaults — Some roles change the default value of certain properties. For instance,
aria-expandedon acomboboxdefaults to"false"rather than"undefined". Be aware of role-specific defaults.
Examples
Incorrect: Misspelled Boolean Value
<div aria-hidden="flase">
This content should be visible to assistive technology.
</div>
The value "flase" is not a valid boolean. Assistive technologies may not be able to interpret the intended state.
Correct: Properly Spelled Boolean Value
<div aria-hidden="false">
This content is visible to assistive technology.
</div>
Incorrect: Invalid Token Value
<button aria-pressed="yes">
Bold
</button>
The aria-pressed attribute accepts "true", "false", or "mixed" — not "yes".
Correct: Valid Token Value
<button aria-pressed="true">
Bold
</button>
Incorrect: Invalid Tristate Value on a Checkbox
<div role="checkbox" aria-checked="partial" tabindex="0">
Select all items
</div>
The aria-checked attribute on a checkbox role only accepts "true", "false", or "mixed". The value "partial" is not recognized.
Correct: Valid Tristate Value on a Checkbox
<div role="checkbox" aria-checked="mixed" tabindex="0">
Select all items
</div>
Incorrect: Invalid Value for aria-sort
<th aria-sort="alphabetical">Name</th>
The aria-sort attribute only accepts "ascending", "descending", "none", or "other".
Correct: Valid Value for aria-sort
<th aria-sort="ascending">Name</th>
Incorrect: Non-Existent ID Reference
<input type="text" aria-labelledby="username-label">
<!-- No element with id="username-label" exists in the document -->
Correct: Valid ID Reference
<label id="username-label">Username</label>
<input type="text" aria-labelledby="username-label">
What This Rule Checks
The aria-valid-attr-value rule inspects every element that has one or more aria- attributes and verifies that each attribute’s value conforms to the allowed values defined in the WAI-ARIA specification. It checks for correct spelling, valid tokens, proper value types (boolean, integer, ID reference, etc.), and ensures that referenced IDs exist in the document.
When you add an ARIA attribute to an HTML element, the browser exposes that information through the accessibility tree so assistive technologies like screen readers can interpret it. If the attribute name is invalid — whether due to a typo like aria-hiden instead of aria-hidden, or a fabricated attribute like aria-visible that doesn’t exist in the spec — the browser won’t recognize it. The attribute is effectively dead code, and the accessibility enhancement you intended never reaches the user.
This is classified as a critical issue because the consequences can be severe. For example, if you misspell aria-required as aria-requried on a form field, screen reader users won’t be informed that the field is mandatory. If you misspell aria-expanded on a disclosure widget, blind and deafblind users won’t know whether a section is open or closed. Keyboard-only users who rely on screen readers are also affected when interactive states and properties fail to communicate correctly.
Related WCAG Success Criteria
This rule maps to WCAG Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that for all user interface components, the name, role, and states/properties can be programmatically determined. Invalid ARIA attributes fail to communicate states and properties to assistive technologies, directly violating this criterion. This applies across WCAG 2.0, 2.1, and 2.2, as well as EN 301 549 (guideline 9.4.1.2).
How to Fix It
-
Audit your ARIA attributes. Review every attribute in your markup that starts with
aria-and confirm it matches a valid attribute name from the WAI-ARIA specification. -
Check for typos. Common mistakes include
aria-labelled-by(correct:aria-labelledby),aria-hiden(correct:aria-hidden), andaria-discribedby(correct:aria-describedby). -
Remove invented attributes. Attributes like
aria-visible,aria-tooltip, oraria-icondo not exist in the WAI-ARIA spec and will have no effect. -
Use tooling. IDE extensions, linters (like
eslint-plugin-jsx-a11y), and the axe DevTools browser extension can catch invalid ARIA attribute names during development.
Common Valid ARIA Attributes
Here are some frequently used ARIA attributes for reference:
-
Widget attributes:
aria-checked,aria-disabled,aria-expanded,aria-hidden,aria-label,aria-pressed,aria-readonly,aria-required,aria-selected,aria-valuenow -
Live region attributes:
aria-live,aria-atomic,aria-relevant,aria-busy -
Relationship attributes:
aria-labelledby,aria-describedby,aria-controls,aria-owns,aria-flowto -
Drag-and-drop attributes:
aria-dropeffect,aria-grabbed
Examples
Incorrect: Misspelled ARIA Attribute
<button aria-expandd="false">Show details</button>
The attribute aria-expandd is not a valid ARIA attribute. Screen readers will not announce the expanded/collapsed state of this button.
Incorrect: Non-Existent ARIA Attribute
<div aria-visible="true">Important announcement</div>
The attribute aria-visible does not exist in the WAI-ARIA specification. It will be completely ignored by assistive technologies.
Incorrect: Typo in a Relationship Attribute
<input type="text" aria-discribedby="help-text">
<p id="help-text">Enter your full name as it appears on your ID.</p>
The attribute aria-discribedby is a misspelling of aria-describedby. The input will not be associated with the help text for screen reader users.
Correct: Properly Spelled ARIA Attributes
<button aria-expanded="false">Show details</button>
<div aria-hidden="true">Decorative content</div>
<input type="text" aria-describedby="help-text">
<p id="help-text">Enter your full name as it appears on your ID.</p>
Each of these examples uses a valid, correctly spelled ARIA attribute that browsers and assistive technologies will recognize and process as intended.
Why This Matters
The autocomplete attribute does more than enable browser autofill — it programmatically communicates the purpose of a form field to assistive technologies. This information is critical for several groups of users:
-
Screen reader users rely on the announced field purpose to understand what information is being requested. Without a valid
autocompletevalue, the screen reader may not convey this context clearly. - Users with cognitive disabilities benefit from browsers and assistive tools that can auto-populate fields or display familiar icons based on the field’s purpose, reducing the mental effort required to complete forms.
- Users with mobility impairments benefit from autofill functionality that minimizes the amount of manual input required.
- Users with low vision may use personalized stylesheets or browser extensions that adapt the presentation of form fields based on their declared purpose (e.g., showing a phone icon next to a telephone field).
This rule maps to WCAG 2.1 Success Criterion 1.3.5: Identify Input Purpose (Level AA), which requires that the purpose of input fields collecting user information can be programmatically determined. The autocomplete attribute is the standard mechanism for satisfying this requirement in HTML.
How the Rule Works
The axe rule autocomplete-valid checks that:
-
The
autocompleteattribute value is a valid token (or combination of tokens) as defined in the HTML specification for autofill. -
The value is appropriate for the type of form control it is applied to (e.g.,
emailis used on an email-type input, not on a checkbox). - The tokens are correctly ordered when multiple tokens are used (e.g., a section name followed by a hint token followed by the field name).
The rule flags fields where the autocomplete value is misspelled, uses a non-existent token, or is applied in an invalid way.
How to Fix It
- Identify all form fields that collect personal user information (name, email, address, phone number, etc.).
- Check if the data type matches one of the 53 input purposes defined in WCAG 2.1 Section 7.
-
Add the correct
autocompletevalue to each matching field. Make sure:- The value is spelled correctly.
- It is appropriate for the input type.
-
If using multiple tokens, they follow the correct order: optional section name (
section-*), optionalshippingorbilling, optionalhome,work,mobile,fax, orpager, and then the autofill field name.
-
Set
autocomplete="off"only when you have a legitimate reason to disable autofill — and note that this does not exempt you from the rule if the field still collects identifiable user data.
Common autocomplete Values
Here are some of the most frequently used values:
| Purpose |
autocomplete Value |
|---|---|
| Full name |
name |
| Given (first) name |
given-name |
| Family (last) name |
family-name |
| Email address |
email |
| Telephone number |
tel |
| Street address |
street-address |
| Postal code |
postal-code |
| Country |
country |
| Credit card number |
cc-number |
| Username |
username |
| New password |
new-password |
| Current password |
current-password |
Examples
Incorrect: Missing or Invalid autocomplete Values
<!-- Missing autocomplete attribute entirely -->
<label for="name">Full Name</label>
<input type="text" id="name" name="name">
<!-- Misspelled autocomplete value -->
<label for="email">Email</label>
<input type="email" id="email" name="email" autocomplete="emal">
<!-- Invalid autocomplete value -->
<label for="phone">Phone</label>
<input type="tel" id="phone" name="phone" autocomplete="phone-number">
In the examples above, the first field has no autocomplete attribute, the second has a typo (emal instead of email), and the third uses a non-standard value (phone-number instead of tel).
Correct: Valid autocomplete Values
<label for="name">Full Name</label>
<input type="text" id="name" name="name" autocomplete="name">
<label for="email">Email</label>
<input type="email" id="email" name="email" autocomplete="email">
<label for="phone">Phone</label>
<input type="tel" id="phone" name="phone" autocomplete="tel">
Correct: Using Multiple Tokens
When a form has separate shipping and billing sections, you can use additional tokens to distinguish them:
<fieldset>
<legend>Shipping Address</legend>
<label for="ship-street">Street Address</label>
<input type="text" id="ship-street" name="ship-street"
autocomplete="shipping street-address">
<label for="ship-zip">Postal Code</label>
<input type="text" id="ship-zip" name="ship-zip"
autocomplete="shipping postal-code">
</fieldset>
<fieldset>
<legend>Billing Address</legend>
<label for="bill-street">Street Address</label>
<input type="text" id="bill-street" name="bill-street"
autocomplete="billing street-address">
<label for="bill-zip">Postal Code</label>
<input type="text" id="bill-zip" name="bill-zip"
autocomplete="billing postal-code">
</fieldset>
Correct: Named Sections with section-*
You can use custom section names to group related fields when the same type of data appears multiple times:
<label for="home-tel">Home Phone</label>
<input type="tel" id="home-tel" name="home-tel"
autocomplete="section-home tel">
<label for="work-tel">Work Phone</label>
<input type="tel" id="work-tel" name="work-tel"
autocomplete="section-work tel">
By using valid, correctly applied autocomplete values, you ensure that assistive technologies can communicate the purpose of each field to users, browsers can offer reliable autofill, and your forms meet the requirements of WCAG 2.1 Success Criterion 1.3.5.
The <blink> element was once used to draw attention to content by making it flash repeatedly. While it may have seemed like an eye-catching effect, it creates serious accessibility barriers for multiple groups of users. The element has been deprecated from the HTML specification, and although most modern browsers no longer render the blinking effect, the element should still be removed from your markup to ensure compliance and avoid issues in any environment that might still support it.
Why this is an accessibility problem
Blinking content affects several groups of users:
- Users with cognitive disabilities may find blinking text distracting or incomprehensible. The constant flashing can make it very difficult to focus on and understand the content.
- Users with low vision struggle to read text that appears and disappears rapidly. The intermittent visibility makes the content effectively unreadable.
- Users with motor disabilities may have difficulty clicking on blinking links or buttons, since the target is not consistently visible and requires precise timing to activate.
- Users with photosensitive conditions can experience discomfort or, in extreme cases, seizures from flashing content, depending on the frequency.
This rule relates to WCAG 2.2 Success Criterion 2.2.2: Pause, Stop, Hide (Level A), which requires that for any moving, blinking, or scrolling content that starts automatically and lasts more than five seconds, users must be able to pause, stop, or hide it. Since the <blink> element provides no mechanism for users to control the flashing, it fails this criterion outright.
This rule also applies to Section 508 §1194.22(j), which states that pages must be designed to avoid causing the screen to flicker with a frequency greater than 2 Hz and lower than 55 Hz.
How to fix it
-
Remove all
<blink>elements from your HTML. Replace them with standard elements like<span>,<strong>, or<em>. -
Remove
text-decoration: blink;from your CSS, as this is the CSS equivalent of the<blink>element. - Use alternative emphasis techniques to make content stand out — bold text, color, larger font size, borders, background highlights, or icons.
Important: Many modern browsers silently ignore the <blink> element, so the text won’t visually blink even though the element is present in the source code. Don’t rely on how the page looks in the browser to determine whether <blink> tags exist. Always check the actual HTML source.
Examples
Incorrect: using the <blink> element
<p><blink>Moving Sale Thursday!</blink></p>
This causes the text to flash on and off (in browsers that support it), making it inaccessible.
Incorrect: using text-decoration: blink in CSS
<style>
.attention {
text-decoration: blink;
}
</style>
<h2 class="attention">Limited Time Offer!</h2>
The CSS text-decoration: blink value achieves the same inaccessible effect as the <blink> element.
Correct: using static visual emphasis
<p><strong>Moving Sale Thursday!</strong></p>
Using <strong> makes the text bold and conveys emphasis to screen readers without any flashing.
Correct: using CSS for visual emphasis without blinking
<style>
.highlight {
background-color: #fff3cd;
border-left: 4px solid #ffc107;
padding: 8px 12px;
font-weight: bold;
}
</style>
<p class="highlight">Limited Time Offer!</p>
This draws attention to the content using color, a border, and bold text — all without any flashing or blinking, keeping the content readable and accessible for everyone.
When a button lacks an accessible name, assistive technologies like screen readers can only announce it generically — for example, as “button” — with no indication of its purpose. This is a critical barrier for people who are blind or deafblind, as they rely entirely on programmatically determined names to understand and interact with interface controls. A sighted user might infer a button’s purpose from an icon or visual context, but without a text-based name, that information is completely lost to assistive technology users.
This rule maps to WCAG 2.0, 2.1, and 2.2 Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that all user interface components have a name that can be programmatically determined. It is also covered by Section 508, EN 301 549 (9.4.1.2), and Trusted Tester guidelines, which require that the purpose of every link and button be determinable from its accessible name, description, or context.
How to fix it
Ensure every <button> element or element with role="button" has an accessible name through one of these methods:
- Visible text content inside the button element.
-
A non-empty
aria-labelattribute that describes the button’s purpose. -
An
aria-labelledbyattribute that references an element containing visible, non-empty text. -
A
titleattribute (use as a last resort, sincetitletooltips are inconsistently exposed across devices).
If a button is purely decorative and should be hidden from assistive technologies, you can assign role="presentation" or role="none" and remove it from the tab order with tabindex="-1". However, this is rare for interactive buttons.
Common mistakes to avoid
-
Leaving a
<button>element completely empty. -
Using only a
valueattribute on a<button>— unlike<input>elements, thevalueattribute on<button>does not provide an accessible name. -
Setting
aria-labelto an empty string (aria-label=""). -
Pointing
aria-labelledbyto an element that doesn’t exist or contains no text. - Using only an icon or image inside a button without providing alternative text.
Examples
Incorrect: empty button
<button id="search"></button>
A screen reader announces this as “button” with no indication of its purpose.
Incorrect: button with only a value attribute
<button id="submit" value="Submit"></button>
The value attribute does not set the accessible name for <button> elements.
Incorrect: empty aria-label
<button id="close" aria-label=""></button>
An empty aria-label results in no accessible name.
Incorrect: aria-labelledby pointing to a missing or empty element
<button id="save" aria-labelledby="save-label"></button>
<div id="save-label"></div>
The referenced element exists but contains no text, so the button has no accessible name.
Correct: button with visible text
<button>Submit order</button>
Correct: icon button with aria-label
<button aria-label="Close dialog">
<svg aria-hidden="true" focusable="false">
<use href="#icon-close"></use>
</svg>
</button>
The aria-label provides the accessible name, while aria-hidden="true" on the SVG prevents duplicate announcements.
Correct: button labeled by another element
<h2 id="section-title">Shopping cart</h2>
<button aria-labelledby="section-title">
<svg aria-hidden="true" focusable="false">
<use href="#icon-arrow"></use>
</svg>
</button>
The button’s accessible name is drawn from the referenced heading text.
Correct: button with aria-label and visible text
<button aria-label="Search products">Search</button>
When both aria-label and inner text are present, aria-label takes precedence as the accessible name. Use this when you need a more descriptive name than what the visible text alone conveys.
Correct: button with title (last resort)
<button title="Print this page">
<svg aria-hidden="true" focusable="false">
<use href="#icon-print"></use>
</svg>
</button>
The title attribute provides an accessible name, but visible text or aria-label are preferred because title tooltips may not be available to touch-screen or keyboard-only users.
Websites typically repeat navigation links, branding, and other interface elements across every page. While sighted mouse users can visually scan past these blocks and click wherever they want, keyboard-only users and screen reader users must navigate through every interactive element sequentially. Without a bypass mechanism, a keyboard user might need to press Tab dozens of times just to reach the primary content on each new page they visit. For users with severe motor impairments, this can take several minutes per page and cause fatigue or physical pain. Even users with less severe limitations will experience frustrating delays compared to mouse users, who can reach any link in a second or two.
Screen reader users also benefit significantly from bypass mechanisms. Landmarks like <main>, <nav>, and <header> allow screen readers to present a structural outline of the page, enabling users to jump directly to the section they need. Headings serve a similar purpose — screen reader users can navigate by heading level to quickly locate the main content area.
This rule maps to WCAG 2.4.1 Bypass Blocks (Level A), which requires that a mechanism is available to bypass blocks of content repeated on multiple pages. It is also required by Section 508 (specifically §1194.22(o)), the Trusted Tester guidelines, and EN 301 549. Because it is a Level A requirement, it represents the minimum baseline for accessibility compliance.
How the Rule Works
The axe bypass rule checks that a page includes at least one of the following:
-
A landmark region (such as
<main>,<nav>,<header>, or<footer>) -
A heading (an
<h1>through<h6>element) - An internal skip link (an anchor link that points to a location further down the page)
If none of these are present, the rule flags the page as a failure.
How to Fix It
The best approach is to use HTML5 landmark elements to structure your page. At a minimum, include a <main> element that wraps the primary content of the page. You should also use <header>, <nav>, and <footer> to identify other common sections. A page should have only one <main> landmark.
Additionally, consider adding a skip navigation link as the very first focusable element on the page. This provides an immediate shortcut for keyboard users who don’t use screen readers and may not be able to navigate by landmarks.
Prefer native HTML5 elements over their ARIA equivalents. For example, use <main> rather than <div role="main">. Native elements are better supported and require less code.
Examples
Incorrect: No Landmarks, Headings, or Skip Links
This page has no structural landmarks, no headings, and no skip link. Keyboard users must tab through every element to reach the content.
<div class="header">
<div class="logo">My Site</div>
<div class="nav">
<a href="/home">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</div>
</div>
<div class="content">
<p>This is the main content of the page.</p>
</div>
<div class="footer">
<p>Footer information</p>
</div>
Correct: Using HTML5 Landmark Elements
Replacing generic <div> wrappers with semantic HTML5 elements gives the page proper structure that assistive technologies can use for navigation.
<header>
<div class="logo">My Site</div>
<nav>
<a href="/home">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
</header>
<main>
<h1>Welcome</h1>
<p>This is the main content of the page.</p>
<section>
<h2>Latest News</h2>
<p>Section content here.</p>
</section>
</main>
<footer>
<p>Footer information</p>
</footer>
Correct: Adding a Skip Navigation Link
A skip link gives keyboard users an immediate way to bypass repeated content. It is typically visually hidden until it receives focus.
<body>
<a class="skip-link" href="#main-content">Skip to main content</a>
<header>
<nav>
<a href="/home">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
</header>
<main id="main-content">
<h1>Page Title</h1>
<p>This is the main content of the page.</p>
</main>
<footer>
<p>Footer information</p>
</footer>
</body>
.skip-link {
position: absolute;
left: -9999px;
top: auto;
width: 1px;
height: 1px;
overflow: hidden;
}
.skip-link:focus {
position: static;
width: auto;
height: auto;
overflow: visible;
}
When the skip link receives keyboard focus, it becomes visible, and pressing Enter moves focus directly to the <main> element. Combined with proper landmark elements, this gives all users fast, reliable access to the page’s primary content.
Color contrast is one of the most common accessibility barriers on the web. When text doesn’t stand out enough from its background, it becomes difficult or impossible to read for many users. People with low vision experience reduced contrast sensitivity, meaning everything appears roughly the same brightness, making it hard to distinguish outlines, edges, and details. Approximately 1 in 12 people cannot see the full spectrum of colors — about 8% of men and 0.4% of women in the United States have some form of color vision deficiency. Nearly three times as many people have low vision compared to total blindness. Without sufficient contrast, these users simply cannot read your content.
This rule maps to WCAG 2.1 Success Criterion 1.4.3: Contrast (Minimum), which is a Level AA requirement. It is also referenced in WCAG 2.0, WCAG 2.2, the Trusted Tester methodology, EN 301 549, and RGAA. The user impact is considered serious because insufficient contrast directly prevents users from perceiving text content.
How Contrast Ratios Work
Contrast ratio is calculated by comparing the relative luminance of two colors on a scale from 1:1 (no contrast, e.g., white on white) to 21:1 (maximum contrast, black on white). WCAG defines two thresholds:
- Normal text (below 18pt or below 14pt bold): minimum 4.5:1 contrast ratio
- Large text (at least 18pt / 24px, or at least 14pt bold / 19px): minimum 3:1 contrast ratio
“Large text” is defined this way because larger characters have wider strokes that are easier to read at lower contrast levels.
What This Rule Checks
The color-contrast rule in axe-core examines each text element on the page and compares the computed foreground text color against the computed background color. It accounts for background color transparency and opacity. Elements that are found to have a 1:1 contrast ratio are flagged as “incomplete” and require manual review.
This rule does not flag:
-
Text elements with a
background-image(these require manual testing) - Text that is visually hidden by other overlapping elements
- Images of text
- Text inside disabled controls (including child elements of disabled buttons)
Some foreground scenarios are harder for automated tools to evaluate, including CSS gradients, pseudo-element backgrounds, backgrounds created with CSS borders, and elements repositioned off-screen with CSS.
How to Fix It
- Identify the failing elements by running axe. Each violation will report the current contrast ratio and the colors involved.
- Adjust the foreground color, background color, or both until the required ratio is met (4.5:1 for normal text, 3:1 for large text).
- Use a contrast checker tool like the axe DevTools browser extension, the WebAIM Contrast Checker, or the built-in color contrast analyzer in browser developer tools to test color combinations before deploying.
- Test with real content — sometimes dynamic content or themed components produce contrast issues that static checks miss.
Examples
Insufficient contrast (fails)
This light gray text on a white background has a contrast ratio of approximately 2.6:1, which fails the 4.5:1 requirement.
<p style="color: #aaaaaa; background-color: #ffffff;">
This text is hard to read.
</p>
Sufficient contrast (passes)
Darkening the text color to #595959 against a white background achieves a contrast ratio of approximately 7:1, meeting the requirement.
<p style="color: #595959; background-color: #ffffff;">
This text is easy to read.
</p>
Large text with lower contrast requirement (passes)
Large text (18pt or larger) only needs a 3:1 contrast ratio. This example uses #767676 on white, which has a ratio of approximately 4.5:1 — well above the 3:1 threshold for large text.
<h1 style="font-size: 24pt; color: #767676; background-color: #ffffff;">
Large heading text
</h1>
Semi-transparent background (fails)
Transparency can reduce effective contrast. Here, the semi-transparent white background doesn’t provide enough contrast depending on what’s behind it.
<div style="background-color: #cccccc;">
<p style="color: #777777; background-color: rgba(255, 255, 255, 0.3);">
Text with a semi-transparent background.
</p>
</div>
Semi-transparent background fixed (passes)
Increasing the background opacity or adjusting colors restores sufficient contrast.
<div style="background-color: #cccccc;">
<p style="color: #333333; background-color: rgba(255, 255, 255, 0.85);">
Text with a more opaque background.
</p>
</div>
Using CSS classes (passes)
In practice, you’ll likely use CSS classes rather than inline styles. Ensure your design system tokens and theme colors meet contrast requirements.
<style>
.card {
background-color: #1a1a2e;
}
.card-text {
color: #e0e0e0;
}
</style>
<div class="card">
<p class="card-text">
Light text on a dark background with good contrast.
</p>
</div>
When a web page uses CSS media queries like @media (orientation: portrait) or @media (orientation: landscape) to force content into a single orientation, it prevents the page from responding to the user’s actual device position. This is checked by the axe rule css-orientation-lock.
Why this matters
Many users physically cannot rotate their devices. People with mobility impairments may have their phone or tablet mounted to a wheelchair, bed, or desk in a fixed orientation. Users with low vision may prefer landscape mode to enlarge text, while others may find portrait easier to read. Locking orientation removes their ability to choose what works best for them.
Beyond motor and vision disabilities, orientation locking also affects users with cognitive disabilities and dyslexia who may rely on a particular layout for readability. Sighted keyboard users who use external displays or stands may also be impacted.
This rule relates to WCAG 2.1 Success Criterion 1.3.4: Orientation (Level AA), which requires that content not restrict its view and operation to a single display orientation unless a specific orientation is essential. Essential use cases are rare — examples include a piano keyboard app, a bank check deposit interface, or a presentation slide display where the orientation is integral to the functionality.
How to fix it
-
Remove orientation-locking CSS. Look for
@mediaqueries that use theorientationfeature combined with styles that effectively hide or completely rearrange content for only one orientation (e.g., settingdisplay: noneorwidth: 0on the body or main content). -
Use responsive design instead. Rather than checking orientation, use
min-widthormax-widthmedia queries to adapt your layout to available space. This naturally accommodates both orientations. - Test in both orientations. Rotate your device or use browser developer tools to simulate both portrait and landscape modes. Verify that all content remains visible and functional.
- Only lock orientation when essential. If your application genuinely requires a specific orientation for core functionality (not just aesthetic preference), document the rationale. This is the only valid exception.
Examples
Incorrect: Locking content to portrait only
This CSS hides the main content area when the device is in landscape orientation, effectively forcing users to use portrait mode:
<style>
@media (orientation: landscape) {
.content {
display: none;
}
.rotate-message {
display: block;
}
}
@media (orientation: portrait) {
.rotate-message {
display: none;
}
}
</style>
<div class="content">
<h1>Welcome to our site</h1>
<p>This content is only visible in portrait mode.</p>
</div>
<div class="rotate-message">
<p>Please rotate your device to portrait mode.</p>
</div>
Incorrect: Using transform to force portrait layout in landscape
<style>
@media (orientation: landscape) {
body {
transform: rotate(-90deg);
transform-origin: top left;
width: 100vh;
height: 100vw;
overflow: hidden;
position: absolute;
}
}
</style>
This forcibly rotates the entire page, fighting against the user’s chosen orientation and creating a confusing, inaccessible experience.
Correct: Responsive layout that works in both orientations
<style>
.content {
padding: 1rem;
}
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
@media (min-width: 600px) {
.grid {
grid-template-columns: 1fr 1fr;
}
}
</style>
<div class="content">
<h1>Welcome to our site</h1>
<div class="grid">
<div>
<p>Column one content.</p>
</div>
<div>
<p>Column two content.</p>
</div>
</div>
</div>
This approach uses min-width instead of orientation, adapting the layout based on available space. The content remains fully accessible and readable whether the device is held in portrait or landscape.
Correct: Using orientation queries for minor style adjustments (not locking)
<style>
.hero-image {
width: 100%;
height: 200px;
object-fit: cover;
}
@media (orientation: landscape) {
.hero-image {
height: 300px;
}
}
</style>
<img class="hero-image" src="hero.jpg" alt="A scenic mountain landscape">
Using orientation media queries is acceptable when you’re making minor visual adjustments without hiding or restricting access to content. The key is that all content and functionality remain available in both orientations.
Screen readers announce definition lists in a specific way, conveying the relationship between terms (<dt>) and their descriptions (<dd>). When a <dl> element contains invalid direct children — such as <p>, <span>, or <li> elements — or when <dt> and <dd> elements appear in the wrong order, assistive technology cannot reliably parse the list. This primarily affects blind and deafblind users who depend on screen readers to understand content structure.
For example, a screen reader might announce a definition list by saying “definition list with 3 items,” then reading each term followed by its definition. If the markup is malformed, the screen reader may skip items, miscount them, or fail to associate terms with their definitions.
This rule maps to WCAG Success Criterion 1.3.1: Info and Relationships (Level A), which requires that information, structure, and relationships conveyed visually are also available programmatically. A properly structured <dl> ensures the semantic relationship between terms and definitions is preserved in the accessibility tree.
How to Fix It
Follow these rules when building definition lists:
-
Direct children of
<dl>must be limited to:<dt>,<dd>,<div>,<script>, or<template>elements. No other elements (like<p>,<span>,<li>, or plain text nodes) should appear as direct children. -
Ordering matters: One or more
<dt>elements must come before one or more<dd>elements. A<dd>should never precede a<dt>within a group. -
Using
<div>as a wrapper: You may wrap a<dt>/<dd>group in a<div>for styling purposes, but each<div>must contain a complete group (at least one<dt>followed by at least one<dd>). -
No stray content: Don’t place bare text or non-allowed elements directly inside the
<dl>.
Examples
Incorrect: Invalid direct child element
The <p> element is not a valid direct child of <dl>.
<dl>
<p>Beverage Types</p>
<dt>Coffee</dt>
<dd>A black hot drink made from roasted beans</dd>
</dl>
Incorrect: Wrong order of <dt> and <dd>
The <dd> element must follow the <dt>, not precede it.
<dl>
<dd>A black hot drink made from roasted beans</dd>
<dt>Coffee</dt>
</dl>
Incorrect: <dd> without a preceding <dt>
Every <dd> must be associated with at least one <dt>.
<dl>
<dd>An orphan definition with no term</dd>
</dl>
Correct: Basic definition list
<dl>
<dt>Coffee</dt>
<dd>A black hot drink made from roasted beans</dd>
<dt>Milk</dt>
<dd>A white cold drink</dd>
</dl>
Correct: Multiple definitions for a single term
<dl>
<dt>Coffee</dt>
<dd>A black hot drink made from roasted beans</dd>
<dd>A stimulating beverage containing caffeine</dd>
</dl>
Correct: Using <div> to wrap groups
Wrapping <dt>/<dd> groups in <div> elements is valid and useful for styling.
<dl>
<div>
<dt>Coffee</dt>
<dd>A black hot drink made from roasted beans</dd>
</div>
<div>
<dt>Milk</dt>
<dd>A white cold drink</dd>
</div>
</dl>
Correct: Multiple terms sharing one definition
<dl>
<dt>Latte</dt>
<dt>Café au lait</dt>
<dd>A coffee drink made with espresso and steamed milk</dd>
</dl>
Ready to validate your sites?
Start your free trial today.