Accessibility Guides for deafblind
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.
The id attribute serves as a unique identifier for an element within an HTML document. When id values are duplicated on active, focusable elements (elements that can receive keyboard focus, like inputs, buttons, links, and elements with tabindex), it creates a fundamental problem: the browser and assistive technologies have no reliable way to distinguish one element from another. This is different from duplicate IDs on non-focusable elements — while still invalid HTML, duplicate IDs on focusable elements have a more direct and serious impact on accessibility.
Why this matters
Screen readers rely on unique id values to build their internal model of the page. When a label references an id via the for attribute, or when aria-labelledby or aria-describedby points to an id, the assistive technology will typically only resolve the first element with that id. This means:
-
Form labels may point to the wrong control. A
<label>usingforto reference a duplicatedidwill only be associated with the first matching element. The second form control becomes unlabeled for screen reader users. -
ARIA relationships break. Attributes like
aria-labelledby,aria-describedby, andaria-controlsdepend on unique IDs to function correctly. -
Table header associations fail. When
<td>elements use theheadersattribute to reference<th>elements byid, duplicated IDs cause incorrect or missing header announcements. -
Client-side scripts malfunction. JavaScript methods like
document.getElementById()return only the first matching element, so event handlers and dynamic behavior may not apply to the intended element.
Users who are blind or deafblind are most seriously affected, as they depend entirely on assistive technology to navigate and interact with focusable elements. The user impact of this issue is considered serious.
While WCAG 2.0’s Success Criterion 4.1.1 (Parsing) originally required valid markup including unique IDs, this criterion was deprecated in WCAG 2.2 because modern browsers handle parsing errors more consistently. However, duplicate active IDs still cause real accessibility failures — particularly violations of SC 1.3.1 (Info and Relationships) and SC 4.1.2 (Name, Role, Value) — because they break the programmatic associations that assistive technology depends on.
How to fix it
-
Identify all focusable elements with duplicate
idvalues. You can use the axe accessibility checker, browser developer tools, or the W3C HTML Validator to find duplicates. -
Assign a unique
idto each focusable element. Append a distinguishing suffix, use a naming convention, or generate unique identifiers. -
Update all references to the renamed IDs, including
<label for="">,aria-labelledby,aria-describedby,aria-controls,headers, and any JavaScript that targets the element byid.
Examples
Incorrect: duplicate id on focusable elements
In this example, two input fields share the same id of "email". The label only associates with the first input, leaving the second input unlabeled for screen reader users.
<label for="email">Personal Email</label>
<input type="email" id="email" name="personal_email">
<label for="email">Work Email</label>
<input type="email" id="email" name="work_email">
Correct: unique id on each focusable element
Each input has a distinct id, and each label correctly references its corresponding control.
<label for="personal-email">Personal Email</label>
<input type="email" id="personal-email" name="personal_email">
<label for="work-email">Work Email</label>
<input type="email" id="work-email" name="work_email">
Incorrect: duplicate id breaking ARIA relationships
Here, two buttons share the same id, so aria-describedby on the dialog can only resolve to the first button — the description association for the second context is lost.
<button id="save-btn" aria-describedby="save-help">Save Draft</button>
<p id="save-help">Saves without publishing.</p>
<button id="save-btn" aria-describedby="publish-help">Save & Publish</button>
<p id="publish-help">Saves and makes content live.</p>
Correct: unique id values with proper ARIA references
<button id="save-draft-btn" aria-describedby="save-help">Save Draft</button>
<p id="save-help">Saves without publishing.</p>
<button id="save-publish-btn" aria-describedby="publish-help">Save & Publish</button>
<p id="publish-help">Saves and makes content live.</p>
Why This Is an Accessibility Problem
In HTML, the id attribute is designed to be a unique identifier for a single element in the document. When two or more elements share the same id, the browser has no reliable way to determine which element is being referenced. This becomes a critical accessibility barrier when that id is used to create relationships between elements — such as linking a <label> to a form input, or connecting a description to a widget via aria-describedby.
Assistive technologies like screen readers rely on these id-based relationships to communicate information to users. When duplicates exist, the screen reader will typically resolve the reference to the first element in the DOM with that id, which may not be the intended target. This means:
- A blind or deafblind user may hear the wrong label for a form field, or no label at all.
-
An ARIA relationship like
aria-labelledbyoraria-describedbymay point to the wrong content, giving users incorrect or missing context. -
Interactive components that depend on
aria-owns,aria-controls, oraria-activedescendantmay break entirely.
This rule relates to WCAG Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that all user interface components have accessible names and roles that can be programmatically determined. Duplicate id values used in ARIA or label associations directly undermine this requirement by creating ambiguous or broken programmatic relationships.
How to Fix It
-
Identify all duplicate
idvalues that are referenced by ARIA attributes (aria-labelledby,aria-describedby,aria-controls,aria-owns,aria-activedescendant, etc.) or by a<label>element’sforattribute. -
Rename the duplicate
idvalues so that each one is unique within the document. -
Update any references to those
idvalues in ARIA attributes orforattributes to match the new unique values. - Verify that each relationship still works correctly by testing with a screen reader or the axe accessibility checker.
Examples
Incorrect: Duplicate id on elements referenced by for
In this example, two inputs share the same id of "email". The second <label> intends to reference the second input, but both for attributes resolve to the first input.
<label for="email">Personal Email</label>
<input type="email" id="email">
<label for="email">Work Email</label>
<input type="email" id="email">
A screen reader user tabbing to the second input would hear no label or the wrong label, making it impossible to know what information to enter.
Correct: Unique id values for each input
<label for="personal-email">Personal Email</label>
<input type="email" id="personal-email">
<label for="work-email">Work Email</label>
<input type="email" id="work-email">
Incorrect: Duplicate id referenced by aria-labelledby
<span id="section-title">Shipping Address</span>
<div role="group" aria-labelledby="section-title">
<!-- shipping fields -->
</div>
<span id="section-title">Billing Address</span>
<div role="group" aria-labelledby="section-title">
<!-- billing fields -->
</div>
Both groups would be announced as “Shipping Address” because the browser resolves both aria-labelledby references to the first <span> with id="section-title".
Correct: Unique id values for each referenced element
<span id="shipping-title">Shipping Address</span>
<div role="group" aria-labelledby="shipping-title">
<!-- shipping fields -->
</div>
<span id="billing-title">Billing Address</span>
<div role="group" aria-labelledby="billing-title">
<!-- billing fields -->
</div>
Incorrect: Duplicate id used in aria-describedby
<p id="hint">Must be at least 8 characters.</p>
<label for="password">Password</label>
<input type="password" id="password" aria-describedby="hint">
<p id="hint">Re-enter your password to confirm.</p>
<label for="confirm-password">Confirm Password</label>
<input type="password" id="confirm-password" aria-describedby="hint">
Correct: Unique id values for each description
<p id="password-hint">Must be at least 8 characters.</p>
<label for="password">Password</label>
<input type="password" id="password" aria-describedby="password-hint">
<p id="confirm-hint">Re-enter your password to confirm.</p>
<label for="confirm-password">Confirm Password</label>
<input type="password" id="confirm-password" aria-describedby="confirm-hint">
The id attribute is the primary mechanism for uniquely identifying an element in the DOM. Many accessibility features depend on id references to create relationships between elements — for example, a <label> element uses its for attribute to point to the id of a form input, and aria-labelledby or aria-describedby attributes reference one or more id values to associate descriptive text with a control.
When two or more elements share the same id, browsers and assistive technologies have no reliable way to determine which element is being referenced. In practice, most screen readers and client-side scripts will act on only the first element with a given id and silently ignore subsequent ones. This means:
- Screen reader users may hear incorrect or missing labels for form fields, table cells, or other interactive elements.
- Keyboard-only users who rely on skip links or in-page anchors may be taken to the wrong location on the page.
- Users of voice control software may be unable to target the correct element by its label.
While duplicate id values are technically an HTML validation error (previously addressed by WCAG 1.0 and the now-deprecated WCAG 2.0 Success Criterion 4.1.1 — Parsing), they remain a practical accessibility concern. Valid markup eliminates an entire category of potential accessibility failures, and ensuring unique id values is one of the simplest ways to maintain it.
How to Fix the Problem
-
Audit your page for duplicate
idvalues. You can use the W3C Markup Validator, browser DevTools, or an accessibility testing tool like axe to find them quickly. -
Rename any duplicated
idvalues so that each one is unique within the document. Choose descriptive, meaningful names that reflect the element’s purpose. -
Update all references to the renamed
id. Search for anyfor,aria-labelledby,aria-describedby,aria-controls,headers, or anchorhrefattributes that pointed to the oldidand update them to match the new value. -
Check dynamically generated content. If your page injects HTML via JavaScript or server-side templates (e.g., rendering a component multiple times in a loop), make sure each instance generates a unique
id, such as by appending an index or a unique identifier.
Examples
Incorrect: Duplicate id Values
In this example, two input elements share the same id of "email". The second label’s for attribute points to "email", but the browser associates it with the first input, leaving the second input effectively unlabeled for assistive technology users.
<label for="email">Personal Email</label>
<input type="email" id="email" name="personal_email">
<label for="email">Work Email</label>
<input type="email" id="email" name="work_email">
Correct: Unique id Values
Each input has a distinct id, and the corresponding label elements reference the correct one.
<label for="personal-email">Personal Email</label>
<input type="email" id="personal-email" name="personal_email">
<label for="work-email">Work Email</label>
<input type="email" id="work-email" name="work_email">
Incorrect: Duplicate id in aria-labelledby References
Here, two sections use the same id for their headings. An aria-labelledby reference on the second region will resolve to the first heading instead.
<section aria-labelledby="section-title">
<h2 id="section-title">Latest News</h2>
<p>News content here.</p>
</section>
<section aria-labelledby="section-title">
<h2 id="section-title">Upcoming Events</h2>
<p>Events content here.</p>
</section>
Correct: Unique id Values for aria-labelledby
<section aria-labelledby="news-title">
<h2 id="news-title">Latest News</h2>
<p>News content here.</p>
</section>
<section aria-labelledby="events-title">
<h2 id="events-title">Upcoming Events</h2>
<p>Events content here.</p>
</section>
Incorrect: Duplicate id from Repeated Components
A common source of duplicate id values is rendering the same component template multiple times.
<div class="card">
<button aria-describedby="card-desc">Buy Now</button>
<p id="card-desc">Free shipping on this item.</p>
</div>
<div class="card">
<button aria-describedby="card-desc">Buy Now</button>
<p id="card-desc">Ships within 2 days.</p>
</div>
Correct: Unique id for Each Component Instance
Append a unique suffix (such as a product ID or index) to each id.
<div class="card">
<button aria-describedby="card-desc-1">Buy Now</button>
<p id="card-desc-1">Free shipping on this item.</p>
</div>
<div class="card">
<button aria-describedby="card-desc-2">Buy Now</button>
<p id="card-desc-2">Ships within 2 days.</p>
</div>
Screen reader users frequently navigate web pages by jumping between headings to get an overview of the content, much like sighted users visually scan a page. When a heading element is empty or its content is hidden from assistive technologies, the screen reader announces something like “heading level 2” with nothing after it. This is disorienting and can make users think content is missing or that the page is broken.
This rule is flagged as a Deque best practice and primarily affects users who are blind or deafblind and rely on screen readers, though it also impacts users with mobility impairments who use heading-based navigation. Well-structured, meaningful headings are foundational to an accessible page — they communicate the document’s outline and help all users find the information they need quickly.
A heading can be effectively “empty” in several ways:
-
The element contains no text at all (
<h2></h2>) - The element contains only whitespace or non-text elements with no accessible name
-
The text is hidden from assistive technologies using
aria-hidden="true"or CSS likedisplay: none
How to fix it
- Add meaningful text to every heading element. Headings should be brief, clear, and descriptive of the section they introduce.
-
Remove heading tags from elements that aren’t actually headings. Don’t use
<h1>through<h6>just for visual styling — use CSS instead. -
Don’t hide heading text from screen readers using
aria-hidden="true"ordisplay: none. If a heading shouldn’t be visible on screen but is needed for accessibility, use a visually-hidden CSS technique instead. -
Maintain a logical heading hierarchy. Headings should be ordered by level (
<h1>, then<h2>, then<h3>, etc.) to accurately convey the structure of the page.
As a quick check, read through only the headings on your page. If they don’t give you a clear sense of the page’s content and structure, rewrite them.
Examples
Empty heading (incorrect)
<h2></h2>
<p>This section has no heading text.</p>
The <h2> is announced by a screen reader, but there’s no content to read.
Heading with only whitespace (incorrect)
<h3> </h3>
Whitespace alone doesn’t provide an accessible name, so this is treated as empty.
Heading hidden from assistive technologies (incorrect)
<h2 aria-hidden="true">About Our Team</h2>
The aria-hidden="true" attribute hides the heading from screen readers entirely, even though sighted users can see it. This creates a gap in the page structure for assistive technology users.
Heading hidden with CSS (incorrect)
<h2 style="display: none;">Contact Us</h2>
Using display: none removes the heading from both the visual layout and the accessibility tree, making it inaccessible to everyone.
Heading with visible text (correct)
<h2>About Our Team</h2>
<p>We are a small company dedicated to accessible design.</p>
The heading clearly describes the section that follows.
Visually hidden heading for screen readers only (correct)
When a heading is needed for document structure but shouldn’t appear visually, use a visually-hidden class rather than display: none or aria-hidden:
<style>
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style>
<h2 class="visually-hidden">Main Navigation</h2>
<nav>
<a href="/home">Home</a>
<a href="/about">About</a>
</nav>
This keeps the heading accessible to screen readers while hiding it visually.
Heading with an image that has alt text (correct)
<h1>
<img src="logo.png" alt="Acme Corporation">
</h1>
The heading’s accessible name comes from the image’s alt attribute, so the heading is not considered empty.
When users navigate a web page using a keyboard or screen reader, they move through what’s called the “focus order” — the sequence of interactive elements that receive focus. Each time an element receives focus, a screen reader announces its role (e.g., “button,” “link,” “checkbox”) along with its name and state. This role is how users understand what type of control they’ve landed on and what actions they can take.
If a focusable element has no role — for example, a <div> made focusable with tabindex="0" — the screen reader may read the element’s text content but will provide no context about what the element is. The user hears text but has no idea whether to press Enter, type input, or toggle a state. Similarly, if an element has an inappropriate role like presentation or an abstract role like widget, assistive technologies cannot convey meaningful interaction patterns.
This issue primarily affects blind and deafblind users who rely on screen readers, and users with motor disabilities who navigate exclusively with a keyboard. Without proper role information, these users cannot efficiently or accurately interact with page controls.
Why Roles Matter
This rule aligns with accessibility best practices recommended by Deque and RGAA. While it doesn’t map to a single WCAG success criterion, it supports several principles:
- WCAG 4.1.2 (Name, Role, Value): All user interface components must expose their role, name, and state to assistive technologies.
- WCAG 2.1.1 (Keyboard): All functionality must be operable through a keyboard. Meaningful role information is essential for keyboard users to understand what they can do with a focused element.
When an element in the focus order has a valid, appropriate role, screen readers can:
- Announce the type of control (e.g., “button,” “textbox,” “menu item”).
- Inform users of available interactions (e.g., “press Enter to activate,” “use arrow keys to navigate”).
- Communicate state changes (e.g., “checked,” “expanded”).
How to Fix the Problem
Use Native HTML Elements First
The simplest and most reliable fix is to use the correct native HTML element. Native elements like <button>, <a>, <input>, and <select> come with built-in roles, keyboard behavior, and accessibility support — no extra attributes needed.
Add ARIA Roles to Custom Widgets
If you must use a non-semantic element (like <div> or <span>) as an interactive control, you need to:
-
Add an appropriate
roleattribute (e.g.,role="button",role="checkbox",role="tab"). - Ensure all required keyboard interactions are implemented.
-
Manage ARIA states and properties (e.g.,
aria-checked,aria-expanded).
Avoid Abstract Roles
ARIA defines abstract roles like command, input, landmark, range, section, sectionhead, select, structure, and widget. These exist only as part of the role taxonomy and must never be used directly on elements. Always use a concrete role instead.
Remove tabindex from Non-Interactive Elements When Possible
If an element doesn’t need to be interactive, consider removing its tabindex attribute entirely so it doesn’t appear in the focus order.
Appropriate Roles for Interactive Content
Here are some common categories of valid roles for focusable elements:
-
Widget roles:
button,checkbox,combobox,link,listbox,menu,menuitem,menuitemcheckbox,menuitemradio,option,radio,scrollbar,slider,spinbutton,switch,tab,textbox,treeitem -
Composite widget roles:
grid,menubar,radiogroup,tablist,toolbar,tree,treegrid -
Landmark roles:
banner,complementary,contentinfo,main,navigation,region,search -
Document structure roles:
dialog,alertdialog,application,document,log,status,timer,tooltip
Examples
Incorrect: Focusable Element With No Role
This <div> can receive focus, but a screen reader won’t announce what it is:
<div tabindex="0" onclick="submitForm()">
Submit
</div>
A screen reader user will hear “Submit” but won’t know it’s meant to be a button.
Correct: Using a Native HTML Button
<button type="button" onclick="submitForm()">
Submit
</button>
The <button> element has a built-in button role. The screen reader announces “Submit, button.”
Correct: Adding an ARIA Role to a Custom Widget
If you cannot use a native <button>, add the appropriate role and keyboard support:
<div role="button" tabindex="0" onclick="submitForm()" onkeydown="handleKeydown(event)">
Submit
</div>
Now the screen reader announces “Submit, button.” You must also implement onkeydown to handle Enter and Space key presses, since a <div> doesn’t natively support those interactions.
Incorrect: Using an Abstract Role
<div role="command" tabindex="0">
Save
</div>
The command role is abstract and must not be used on elements. Assistive technologies will not recognize it as a valid role.
Correct: Replacing the Abstract Role
<div role="button" tabindex="0" onkeydown="handleKeydown(event)">
Save
</div>
Incorrect: Non-Interactive Role on a Focusable Element
<span role="paragraph" tabindex="0" onclick="openMenu()">
Options
</span>
The paragraph role is not interactive. The element will not behave as expected for assistive technology users, and may not even receive focus in some screen readers.
Correct: Using an Appropriate Widget Role
<span role="button" tabindex="0" onclick="openMenu()" onkeydown="handleKeydown(event)">
Options
</span>
Incorrect: Focusable Custom Checkbox Without a Role
<div tabindex="0" class="custom-checkbox" onclick="toggleCheck(this)">
Accept terms
</div>
Correct: Custom Checkbox With Proper Role and State
<div role="checkbox" tabindex="0" aria-checked="false" onclick="toggleCheck(this)" onkeydown="handleKeydown(event)">
Accept terms
</div>
The role="checkbox" tells the screen reader this is a checkbox, and aria-checked communicates its current state. Remember to update aria-checked in your JavaScript when the state changes.
When a form field has more than one <label> element pointing to it (either via the for attribute or by nesting), assistive technologies have no reliable way to determine which label is the correct one. This inconsistency means that users who are blind, have low vision, or are deafblind may hear the wrong label, an incomplete label, or a confusing combination of labels when interacting with a form. Users with mobility impairments also benefit from properly associated labels, since a single clear <label> expands the clickable area of the associated input.
This rule relates to WCAG 2.0, 2.1, and 2.2 Success Criterion 3.3.2: Labels or Instructions (Level A), which requires that labels or instructions are provided when content requires user input. Multiple conflicting labels undermine this requirement because the user cannot reliably receive a single, clear label for the field.
How to Fix
Ensure that each form field has only one <label> element associated with it. You can associate a label with a field in one of two ways — but use only one label per field:
-
Explicit association — Use the
forattribute on the<label>matching theidof the input. -
Implicit association — Wrap the input inside the
<label>element.
If you need to provide additional descriptive text beyond the label, use aria-describedby to point to supplementary instructions rather than adding a second <label>.
If you have a situation where one label must be visually hidden, hide the redundant label using CSS (display: none or visibility: hidden) so it is fully removed from the accessibility tree, and remove its for attribute. Using aria-hidden="true" alone on a <label> is not sufficient to prevent all screen readers from associating it with the field.
Examples
Incorrect: Two explicit labels for one input
Both <label> elements use for="username", causing unpredictable screen reader behavior.
<label for="username">Username</label>
<label for="username">Enter your username</label>
<input type="text" id="username" />
Incorrect: One explicit and one implicit label
The input is both wrapped in a <label> and referenced by another <label> via for.
<label for="email">Email</label>
<label>
Email address:
<input type="text" id="email" />
</label>
Incorrect: Nested labels
Labels should never be nested inside each other.
<label>
Enter your comments:
<label>
Comments:
<textarea id="comments"></textarea>
</label>
</label>
Correct: Single explicit label
One <label> with a for attribute matching the input’s id.
<label for="username">Username</label>
<input type="text" id="username" />
Correct: Single implicit label
The input is wrapped inside a single <label>.
<label>
Email address:
<input type="text" id="email" />
</label>
Correct: Label with supplementary instructions using aria-describedby
When you need to provide extra guidance beyond the label, use aria-describedby instead of a second label.
<label for="password">Password</label>
<input type="password" id="password" aria-describedby="password-hint" />
<p id="password-hint">Must be at least 8 characters with one number.</p>
Correct: Using the title attribute as a label
When a visible label is not appropriate (rare cases), the title attribute can serve as an accessible name.
<textarea id="search" title="Search terms"></textarea>
Correct: Select inside a single label
<label>
Choose an option:
<select id="options">
<option selected>Option A</option>
<option>Option B</option>
</select>
</label>
Why This Matters
Web pages often embed content using iframe or frame elements — for ads, third-party widgets, embedded forms, video players, or even internal application components. Each frame is essentially its own document with its own DOM. If axe-core is not running inside those frames, any accessibility violations within them go completely undetected.
This is classified as a critical user impact issue — not because the frame itself is inaccessible, but because undetected violations inside frames can affect all users with disabilities. Blind users relying on screen readers, sighted keyboard-only users, and deafblind users could all encounter barriers hidden within untested frame content. Missing form labels, broken focus management, insufficient color contrast, or missing alternative text inside frames would remain invisible to your testing process.
This rule is a Deque best practice rather than a specific WCAG success criterion. However, the violations that go undetected inside untested frames can relate to numerous WCAG criteria, including 1.1.1 (Non-text Content), 1.3.1 (Info and Relationships), 2.1.1 (Keyboard), 2.4.3 (Focus Order), 4.1.2 (Name, Role, Value), and many others. Comprehensive accessibility testing requires that all content on a page is analyzed, including content within frames.
How the Rule Works
When the iframes configuration property is set to true, axe-core attempts to run inside every iframe and frame element on the page. The rule uses both frame and iframe selectors to check whether the axe-core script is present in each frame’s document. If axe-core is not found inside a frame, the rule returns a “needs review” result for that element.
The rule operates at the page level — meaning results from nodes across different frames are combined into a single result when the entire page is tested. An optional after function processes results from all frames together.
How to Fix It
There are several approaches to ensure frames are properly tested:
-
Use axe-core’s built-in iframe support. When running axe-core programmatically, set the
iframesoption totrue(this is the default). axe-core will automatically attempt to communicate with frames to gather results — but the axe-core script must be present in each frame for this to work. -
Inject axe-core into all frames. If you control the frame content, include the axe-core script in those documents. If you’re using a testing framework like Selenium, Puppeteer, or Playwright, inject the axe-core script into each frame before running the analysis.
-
Use axe DevTools browser extension. The axe DevTools extension handles frame injection automatically in most cases, making it the simplest option for manual testing.
-
For third-party frames you don’t control, acknowledge that testing coverage is limited and consider testing the third-party content separately, or document it as an area requiring manual review.
Examples
Frame that triggers the issue
An iframe is present on the page, but axe-core is not loaded inside it. The content within the frame remains untested:
<main>
<h1>Our Application</h1>
<iframe src="https://example.com/embedded-form" title="Contact form"></iframe>
</main>
If https://example.com/embedded-form does not have axe-core loaded, axe will flag this frame as needing review.
Correct setup with axe-core injected
When using a testing tool like Playwright, ensure axe-core is injected into all frames:
const { AxeBuilder } = require('@axe-core/playwright');
// AxeBuilder automatically analyzes iframe content
const results = await new AxeBuilder({ page })
.analyze();
Frame content that includes axe-core
If you control the frame source, include the axe-core script directly:
<!-- Content inside the iframe document -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Embedded Form</title>
<script src="/vendor/axe-core/axe.min.js"></script>
</head>
<body>
<form>
<label for="email">Email address</label>
<input type="email" id="email" name="email">
<button type="submit">Subscribe</button>
</form>
</body>
</html>
Properly structured frame on the parent page
Always ensure your frames have descriptive title attributes so users understand their purpose:
<iframe src="/embedded-form.html" title="Newsletter subscription form"></iframe>
This won’t fix the axe-core injection issue by itself, but it ensures the frame is accessible while you work on getting full test coverage inside it.
Screen reader users rely on frame titles to understand what each embedded region of a page contains. Many screen readers offer a feature that lists all frames on a page by their titles, allowing users to jump directly to the one they need. When frames lack titles or share identical titles, this feature becomes useless — users are left guessing which frame is which, or the screen reader falls back to unhelpful information like “frame,” “javascript,” a filename, or a URL.
This issue primarily affects users who are blind, deafblind, or who navigate with assistive technologies. It relates 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 an accessible name that can be programmatically determined. When frames share a title, their accessible names fail to uniquely identify them, violating this criterion. The rule is also covered by Trusted Tester guideline 12.D, which requires that the combination of accessible name and description for each <iframe> describes its content, as well as EN 301 549 section 9.4.1.2.
How to Fix It
-
Add a
titleattribute to every<frame>and<iframe>element. - Make each title unique across the page. No two frames should share the same title.
- Make each title descriptive. The title should clearly summarize the content or purpose of the frame. Avoid generic labels like “frame” or “untitled.”
-
Match the frame’s inner document
<title>to thetitleattribute on the frame element when possible. Some screen readers replace the frame’stitleattribute with the inner document’s<title>element, so keeping them consistent ensures a reliable experience.
Tips for Writing Good Frame Titles
- Keep titles brief but informative — describe what the frame contains, not just that it exists.
- Put the most distinctive information first. If you include a brand name, place it at the end so users don’t have to hear it repeatedly while scanning a list of frames.
- Replace placeholder titles like “untitled” or “page” with meaningful descriptions.
- If the framed content has a visible heading, consider aligning the frame title with that heading for consistency.
Examples
Incorrect: Frames with Duplicate Titles
In this example, two iframes share the same title, making it impossible for screen reader users to tell them apart.
<iframe src="/news.html" title="Company Updates"></iframe>
<iframe src="/events.html" title="Company Updates"></iframe>
Incorrect: Frame with an Empty Title
An empty title provides no useful information to assistive technology users.
<iframe src="/contact.html" title=""></iframe>
Incorrect: Frames with No Title
Without any title attribute, screen readers fall back to announcing unhelpful information like the URL or “frame.”
<iframe src="/navigation.html"></iframe>
<iframe src="/main-content.html"></iframe>
Correct: Frames with Unique, Descriptive Titles
Each frame has a distinct title that clearly describes its content.
<iframe src="/news.html" title="Latest Company News"></iframe>
<iframe src="/events.html" title="Upcoming Events Calendar"></iframe>
Correct: Frame Title Matching the Inner Document Title
For the best experience, align the title attribute with the <title> element in the framed document.
<!-- Parent page -->
<iframe src="/contact.html" title="Contact Form"></iframe>
<!-- contact.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>Contact Form</title>
</head>
<body>
<h1>Contact Us</h1>
<form>
<label for="email">Email</label>
<input type="email" id="email" name="email">
<button type="submit">Send</button>
</form>
</body>
</html>
Correct: Hiding Decorative or Non-Content Frames
If a frame is purely decorative or contains no meaningful content for users (such as a tracking pixel), hide it from assistive technology entirely instead of giving it a misleading title.
<iframe src="/tracking-pixel.html" title="" aria-hidden="true" tabindex="-1"></iframe>
Note that this approach should only be used when the frame genuinely contains no content that any user would need to access.
Screen reader users can pull up a list of all frames on a page and navigate between them using their titles. When frames lack accessible names, users hear generic labels like “frame,” “JavaScript,” or a raw URL — none of which convey the purpose or content of the frame. This forces users to enter each frame just to figure out what it contains, which is time-consuming and frustrating.
This issue primarily affects users who are blind or deafblind and rely on assistive technology, but it also impacts users with mobility disabilities who navigate by frame lists or landmarks.
Relevant WCAG Success Criteria
This rule relates to WCAG 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. Frames are interactive containers that users navigate into and out of, so they must have an accessible name.
It is also explicitly required by Section 508 (§1194.22(i)), which states that frames shall be titled with text that facilitates frame identification and navigation.
How to Fix It
Give every <iframe> and <frame> element a clear, descriptive accessible name using one of these methods:
-
titleattribute — The most common approach. Add atitlethat briefly describes the frame’s content. -
aria-labelattribute — Provides an accessible name directly on the element. -
aria-labelledbyattribute — References another element’s text as the accessible name.
Tips for Writing Good Frame Titles
- Be brief and descriptive. A title like “Product search results” is far more useful than “frame1” or “content.”
- Make each title unique. If you have multiple frames, each one should have a distinct title so users can tell them apart.
-
Match the frame’s internal document title. Some screen readers replace the frame’s
titleattribute with the<title>element inside the framed document. For consistency, keep them the same or very similar. - Avoid placeholder text. Titles like “untitled” or “iframe” provide no useful information.
- Put unique information first. If you include a brand name, place it after the descriptive content so users hear the most useful information immediately.
Examples
Incorrect: <iframe> Without an Accessible Name
The frame has no title, aria-label, or aria-labelledby, so screen readers cannot describe it.
<iframe src="https://example.com/video-player"></iframe>
Incorrect: Empty title Attribute
An empty title is the same as having no title at all.
<iframe src="https://example.com/video-player" title=""></iframe>
Correct: Using the title Attribute
<iframe src="https://example.com/video-player" title="Product demo video player"></iframe>
Correct: Using aria-label
<iframe src="https://example.com/map" aria-label="Store location map"></iframe>
Correct: Using aria-labelledby
<h2 id="chat-heading">Live Chat Support</h2>
<iframe src="https://example.com/chat" aria-labelledby="chat-heading"></iframe>
Correct: Multiple Frames With Unique Titles
When a page contains several frames, each one should have a distinct, descriptive title.
<iframe src="/navigation" title="Site navigation menu"></iframe>
<iframe src="/content" title="Main article content"></iframe>
<iframe src="/ads" title="Sponsored advertisements"></iframe>
Correct: Matching the Frame Title to the Document Title
For the best experience, align the title attribute on the <iframe> with the <title> element inside the framed document.
<!-- Parent page -->
<iframe src="contact-form.html" title="Contact us form"></iframe>
<!-- contact-form.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>Contact us form</title>
</head>
<body>
<h1>Contact Us</h1>
<form>
<label for="email">Email</label>
<input type="email" id="email">
<button type="submit">Send</button>
</form>
</body>
</html>
Headings (h1 through h6) serve as a structural outline of your page. Sighted users can visually scan a page and understand its organization through font sizes and visual weight, but screen reader users depend entirely on properly marked-up heading levels to achieve the same understanding. When heading levels are skipped — for example, jumping from an h2 to an h4 — it creates confusion because users can’t tell whether they missed a subsection or if the structure is simply incorrect.
This rule primarily affects users who are blind, deafblind, or have mobility impairments and rely on assistive technology to navigate. Screen readers provide keyboard shortcuts that let users jump between headings, effectively creating a table of contents for the page. If the heading hierarchy has gaps, users may waste time trying to find content they assume exists in the missing level, or they may misunderstand the relationship between sections.
This is a Deque best practice rule. While it aligns with the intent of WCAG Success Criterion 1.3.1 (Info and Relationships) and Success Criterion 2.4.6 (Headings and Labels), the sequential ordering of heading levels is not an explicit WCAG requirement. However, maintaining a logical heading order is widely considered essential for usable, accessible content.
How to Fix It
-
Audit your heading structure. Review all headings on the page and verify they follow a sequential order. An
h1should be followed by anh2, anh2by anh3, and so on. You can go back up to a higher level at any time (e.g., fromh3back toh2), but you should never skip levels going down. -
Start main content with an
h1. Screen reader users can jump directly to the firsth1on a page, so placing it at the beginning of your main content lets them skip navigation and other preamble. - Don’t use headings for visual styling. If you need text to look bigger or bolder, use CSS instead of heading elements. Heading markup should only be used for actual headings that describe the content that follows.
- Read only the headings. A quick test: read through just the headings on your page. Do they give you a clear sense of the page’s structure and content? If not, revise them.
Examples
Incorrect: Skipped Heading Levels
This example jumps from h1 to h3, skipping the h2 level entirely.
<h1>Photography Basics</h1>
<p>Learn the fundamentals of photography.</p>
<h3>Understanding ISO</h3>
<p>ISO controls the sensor's sensitivity to light.</p>
<h3>Choosing an Aperture</h3>
<p>Aperture affects depth of field and light intake.</p>
Correct: Sequential Heading Levels
The headings follow a logical order without skipping any levels.
<h1>Photography Basics</h1>
<p>Learn the fundamentals of photography.</p>
<h2>Understanding ISO</h2>
<p>ISO controls the sensor's sensitivity to light.</p>
<h2>Choosing an Aperture</h2>
<p>Aperture affects depth of field and light intake.</p>
Correct: Deeper Nesting with Proper Hierarchy
When content has subsections, each level increments by one. You can return to a higher level when starting a new major section.
<h1>Setting Exposure Manually on a Camera</h1>
<p>Manual exposure involves three key settings.</p>
<h2>Set the ISO</h2>
<p>Start by choosing an ISO value.</p>
<h3>Low Light Conditions</h3>
<p>Use a higher ISO in dim environments.</p>
<h3>Bright Light Conditions</h3>
<p>Use a lower ISO outdoors in sunlight.</p>
<h2>Choose an Aperture</h2>
<p>Aperture is measured in f-stops.</p>
<h2>Set a Shutter Speed</h2>
<p>Shutter speed controls motion blur.</p>
Incorrect: Using Headings for Visual Styling
Here, an h4 is used not because it fits the document hierarchy but because the developer wanted smaller text.
<h1>Our Services</h1>
<p>We offer a range of professional services.</p>
<h4>Contact us today for a free quote!</h4>
Correct: Using CSS Instead of Heading Markup for Styling
<h1>Our Services</h1>
<p>We offer a range of professional services.</p>
<p class="callout">Contact us today for a free quote!</p>
Screen readers rely on distinct sound libraries for each language, with pronunciation rules, intonation patterns, and phonetics tailored to that specific language. When a user navigates to a page, the screen reader checks the lang attribute on the <html> element to determine which sound library to load. If no lang attribute is present, the screen reader falls back to whatever default language the user configured during setup. For someone who speaks multiple languages and browses websites in different languages, this creates a serious problem — the screen reader will attempt to read foreign-language content using the wrong pronunciation rules, producing garbled or incomprehensible speech.
This issue primarily affects blind and deafblind users who rely on screen readers, but it also impacts users with cognitive disabilities who may use text-to-speech tools. When text is read aloud with the wrong language rules, even simple words become unrecognizable, effectively making the content inaccessible.
Related WCAG Success Criteria
This rule maps to WCAG 2.0, 2.1, and 2.2 Success Criterion 3.1.1: Language of Page at Level A — the most fundamental level of conformance. This criterion requires that the default human language of each web page can be programmatically determined. It is also referenced by the Trusted Tester guidelines (11.A), EN 301 549, and RGAA.
How to Fix It
Add a lang attribute with a valid language code to the opening <html> element. The value should represent the primary language of the page content.
You can use a simple two-letter language code (like en for English, fr for French, or de for German), or you can be more specific with a regional dialect code like en-US for American English or fr-CA for Canadian French. A full list of valid language subtags is available in the IANA Language Subtag Registry.
If sections of your page contain content in a different language than the primary one, use the lang attribute on those specific elements so screen readers can switch sound libraries mid-page.
For languages written right to left (such as Arabic or Hebrew), also include the dir attribute with a value of rtl to indicate the text direction.
Examples
Incorrect: Missing lang attribute
This will trigger the rule because the <html> element has no lang attribute:
<html>
<head>
<title>My Website</title>
</head>
<body>
<h1>Welcome</h1>
</body>
</html>
Correct: lang attribute with a valid language code
<html lang="en">
<head>
<title>My Website</title>
</head>
<body>
<h1>Welcome</h1>
</body>
</html>
Correct: Using a regional dialect code
<html lang="en-US">
<head>
<title>My Website</title>
</head>
<body>
<h1>Welcome</h1>
</body>
</html>
Correct: Handling inline language changes
When a portion of the page is in a different language, use the lang attribute on the containing element:
<html lang="en">
<head>
<title>My Website</title>
</head>
<body>
<p>The French word for hello is <span lang="fr">bonjour</span>.</p>
</body>
</html>
Correct: Right-to-left language with dir attribute
<html lang="ar" dir="rtl">
<head>
<title>موقعي</title>
</head>
<body>
<h1>مرحبا</h1>
</body>
</html>
Screen readers rely on language-specific sound libraries to pronounce text accurately. Each language has its own pronunciation rules, intonation patterns, and phonetic characteristics, so screen readers load the appropriate library based on the declared language of the document. When the <html> element has no lang attribute or contains an invalid value — such as a misspelling or a fabricated code — the screen reader cannot determine which library to use. It defaults to whatever language the user configured during setup, which means content written in French might be read aloud using English pronunciation rules. The result is speech that sounds like a confusing, unintelligible accent.
This issue primarily affects blind users and deafblind users who rely on screen readers, but it also impacts users with cognitive disabilities who may use text-to-speech tools to aid comprehension. When pronunciation is wrong, understanding drops dramatically.
Related WCAG Success Criteria
This rule maps to WCAG Success Criterion 3.1.1: Language of Page (Level A), which requires that the default human language of each web page can be programmatically determined. This is a Level A requirement — the most fundamental level of accessibility — meaning it must be satisfied for basic compliance under WCAG 2.0, 2.1, and 2.2. It is also required by the Trusted Tester methodology, EN 301 549, and RGAA.
How to Fix It
-
Add a
langattribute to the opening<html>element. - Set its value to a valid BCP 47 language tag that represents the primary language of the page.
-
Double-check the spelling of the language code. Common valid codes include
"en"(English),"fr"(French),"es"(Spanish),"de"(German),"ja"(Japanese), and"ar"(Arabic). -
You can optionally include a regional subtag, such as
"en-US"for American English or"fr-CA"for Canadian French. -
For XHTML documents served as XML, use
xml:langinstead of or in addition tolang.
If the language changes within the document for specific passages, use the lang attribute on the appropriate element to indicate the switch. For right-to-left languages, also include the dir="rtl" attribute.
Examples
Incorrect: Missing lang attribute
<html>
<head>
<title>My Page</title>
</head>
<body>
<p>Welcome to my website.</p>
</body>
</html>
Without a lang attribute, screen readers cannot determine the page language and will default to the user’s configured language, which may not match the content.
Incorrect: Invalid lang value
<html lang="english">
<head>
<title>My Page</title>
</head>
<body>
<p>Welcome to my website.</p>
</body>
</html>
The value "english" is not a valid BCP 47 language tag. The correct code for English is "en".
Correct: Valid lang attribute
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<p>Welcome to my website.</p>
</body>
</html>
Correct: Regional subtag
<html lang="fr-CA">
<head>
<title>Ma page</title>
</head>
<body>
<p>Bienvenue sur mon site web.</p>
</body>
</html>
Correct: Language change within the page
<html lang="en">
<head>
<title>Multilingual Page</title>
</head>
<body>
<p>This paragraph is in English.</p>
<p lang="es">Este párrafo está en español.</p>
</body>
</html>
Correct: Right-to-left language
<html lang="ar" dir="rtl">
<head>
<title>صفحتي</title>
</head>
<body>
<p>مرحبا بكم في موقعي.</p>
</body>
</html>
The dir="rtl" attribute ensures the text direction is correctly rendered for right-to-left languages like Arabic and Hebrew.
The lang attribute tells browsers and assistive technologies what language the content is written in. The xml:lang attribute serves the same purpose but comes from the XML/XHTML specification. When both attributes are present on the same element—most commonly the <html> element—they must agree on the base language. The “base language” is the primary language subtag (e.g., en in en-US or fr in fr-CA). If one attribute says en and the other says fr, assistive technologies receive conflicting information and cannot reliably determine how to process the content.
Who is affected
This issue primarily impacts screen reader users, including people who are blind, deafblind, or have cognitive disabilities. Screen readers maintain separate pronunciation libraries for each language. When they encounter a language declaration, they switch to the appropriate library so words are spoken with correct pronunciation and cadence. A mismatch between lang and xml:lang can cause the screen reader to select the wrong library or behave unpredictably, making the content difficult or impossible to understand.
Users who speak multiple languages and browse sites in different languages are especially vulnerable, as they rely on accurate language declarations to switch between language contexts.
Related WCAG success criteria
This rule relates to WCAG Success Criterion 3.1.1: Language of Page (Level A), which requires that the default human language of each web page can be programmatically determined. This criterion applies across WCAG 2.0, 2.1, and 2.2, as well as EN 301 549 and RGAA. When lang and xml:lang conflict, the programmatically determined language becomes ambiguous, violating this requirement.
How to fix it
-
Check the
<html>element (and any other elements) for the presence of bothlangandxml:langattributes. -
Ensure both attributes use the same base language. For example, if
lang="en", thenxml:langmust also start withen(e.g.,en,en-US, oren-GB). -
If you don’t need
xml:lang, the simplest fix is to remove it entirely. Thelangattribute alone is sufficient for HTML5 documents. - If you’re serving XHTML, both attributes may be required. In that case, keep them in sync.
You can find valid language codes on the ISO 639 language codes reference page. Common codes include en for English, fr for French, es for Spanish, de for German, and ar for Arabic.
Examples
Incorrect: mismatched base languages
The lang attribute specifies English, but xml:lang specifies French. This creates a conflict that confuses assistive technologies.
<html lang="en" xml:lang="fr">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Correct: matching base languages
Both attributes specify English. The dialect subtag may differ, but the base language (en) is the same.
<html lang="en-US" xml:lang="en-GB">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Correct: identical values
The simplest and most reliable approach—use the exact same value for both attributes.
<html lang="en" xml:lang="en">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Correct: only lang attribute (HTML5)
In HTML5 documents, the xml:lang attribute is not required. Using lang alone avoids any possibility of a mismatch.
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Correct: specifying language on inline elements
When content in a different language appears within the page, use the lang attribute on the appropriate element. If you also use xml:lang, make sure they match.
<p>Welcome to our site. <span lang="es" xml:lang="es">Bienvenidos a nuestro sitio.</span></p>
For right-to-left languages, also include the dir attribute:
<p lang="ar" xml:lang="ar" dir="rtl">مرحبا بكم في موقعنا</p>
When screen reader users navigate a page, they often pull up a list of all links to quickly scan available destinations. If that list contains several links all labeled “Read more,” there’s no way to tell them apart. This creates confusion and forces users to leave the link list, find each link in context, and read the surrounding content to understand its purpose. Users who are blind or deafblind are most affected, but this issue also impacts anyone who relies on link text to understand navigation options.
This rule relates to WCAG Success Criterion 2.4.9: Link Purpose (Link Only), a Level AAA requirement. It states that the purpose of each link should be determinable from the link text alone. While the related criterion 2.4.4 (Level A) allows link purpose to be determined from context, SC 2.4.9 sets a higher bar: each link’s text must be self-explanatory on its own. Additionally, Success Criterion 3.2.4: Consistent Identification requires that components with the same functionality are identified consistently. Together, these criteria mean that links with the same name should go to the same place, and links that go to different places should have different names.
How to Fix the Problem
The key principle is straightforward: if two links have the same accessible name, they should serve the same purpose. If they serve different purposes, give them distinct accessible names.
Here are practical ways to fix this:
- Write descriptive link text. Instead of generic phrases like “Read more” or “Click here,” write link text that describes the destination, such as “Read the accessibility policy” or “View January’s meeting minutes.”
-
Use
aria-labelto differentiate links. When the visible text must be generic (e.g., for design reasons), usearia-labelto provide a unique, descriptive name for each link. -
Use
aria-labelledbyto combine visible headings or other text with the link to form a unique accessible name. -
Provide meaningful
alttext on image links. If a link contains only an image, the image’saltattribute becomes the link’s accessible name. Make sure it describes the link’s destination.
Examples
Incorrect: Multiple links with the same name going to different pages
In this example, two “Read more” links appear identical to assistive technology but lead to entirely different articles:
<h3>Neighborhood News</h3>
<p>Seminole tax hike: City managers propose a 75% increase in property taxes.
<a href="taxhike.html">Read more...</a>
</p>
<p>Baby Mayor: Voters elect the city's youngest mayor ever.
<a href="babymayor.html">Read more...</a>
</p>
A screen reader listing all links on this page would show two identical entries — “Read more…” — with no way to distinguish them.
Correct: Using aria-label to differentiate links
By adding an aria-label, each link gets a unique accessible name while keeping the visible text short:
<h3>Neighborhood News</h3>
<p>Seminole tax hike: City managers propose a 75% increase in property taxes.
<a href="taxhike.html" aria-label="Read more about Seminole tax hike">Read more...</a>
</p>
<p>Baby Mayor: Voters elect the city's youngest mayor ever.
<a href="babymayor.html" aria-label="Read more about Seminole's new baby mayor">Read more...</a>
</p>
Correct: Writing descriptive link text directly
The simplest approach is to make the link text itself descriptive:
<a href="routes.html">Current routes at Boulders Climbing Gym</a>
Correct: Descriptive alt text on an image link
When a link wraps an image, the alt attribute serves as the link’s accessible name:
<a href="routes.html">
<img src="topo.gif" alt="Current routes at Boulders Climbing Gym">
</a>
Correct: Image and text together in a link
When a link contains both an image and text, use an empty alt attribute on the image to avoid redundancy. The visible text becomes the accessible name:
<a href="routes.html">
<img src="topo.gif" alt="">
Current routes at Boulders Climbing Gym
</a>
Correct: Links with the same name pointing to the same destination
If two links genuinely go to the same place, it’s fine for them to share the same name:
<nav>
<a href="/contact">Contact us</a>
</nav>
<footer>
<a href="/contact">Contact us</a>
</footer>
Both links are labeled “Contact us” and both lead to the same contact page, so there is no ambiguity.
Correct: Using aria-labelledby to build a unique name from existing content
You can reference a nearby heading to create a unique accessible name without adding extra visible text:
<h3 id="tax-heading">Seminole Tax Hike</h3>
<p>City managers propose a 75% increase in property taxes.
<a href="taxhike.html" aria-labelledby="tax-heading tax-link">
<span id="tax-link">Read more</span>
</a>
</p>
<h3 id="mayor-heading">Baby Mayor Elected</h3>
<p>Voters elect the city's youngest mayor ever.
<a href="babymayor.html" aria-labelledby="mayor-heading mayor-link">
<span id="mayor-link">Read more</span>
</a>
</p>
The first link’s accessible name resolves to “Seminole Tax Hike Read more” and the second to “Baby Mayor Elected Read more,” making them distinguishable in a links list.
Images are a fundamental part of web content, but they are inherently visual. Screen readers cannot interpret the pixels of an image — they rely entirely on a text alternative to describe the image’s content and purpose to the user. Without alternative text, a screen reader may read out the image’s file name (e.g., “IMG_20230415_093012.jpg”), which is meaningless and confusing. In the worst case, the image is silently skipped, and the user misses critical information.
This rule primarily affects people who are blind or deafblind, who depend on screen readers or braille displays to access content. Users with low vision who rely on text magnification or high-contrast modes can also benefit from well-written alternative text.
Why This Matters
This rule maps to WCAG 2.2 Success Criterion 1.1.1: Non-text Content (Level A), which requires that all non-text content presented to the user has a text alternative that serves an equivalent purpose. It is also required by Section 508 and EN 301 549. Because it is a Level A requirement, it represents the absolute minimum baseline for accessibility — failing it means your content is fundamentally inaccessible to many users.
How to Provide Alternative Text
There are three main techniques for giving an image accessible alternative text:
-
The
altattribute — the most common and preferred method for<img>elements. -
The
aria-labelattribute — provides an accessible name directly on the element. -
The
aria-labelledbyattribute — points to theidof another element that contains the descriptive text.
Writing Good Alt Text
When writing alternative text, consider these questions:
- Why is this image here? What role does it play in the content?
- What information does it convey? What would a sighted user take away from it?
- If you removed the image, what words would replace it to preserve the same meaning?
Keep alt text concise and descriptive. Avoid phrases like “image of” or “picture of” — screen readers already announce the element as an image. Don’t use the file name as alt text. Focus on the image’s purpose and meaning, not a pixel-by-pixel description.
Handling Decorative Images
Not every image carries meaning. Decorative images — such as visual separators, background flourishes, or images whose content is fully described in surrounding text — should be hidden from assistive technology. To do this, use an empty alt attribute: alt="".
Do not omit the alt attribute entirely. If alt is missing, screen readers will often fall back to reading the file name, which creates a worse experience than having no announcement at all. An empty alt="" explicitly tells the screen reader to skip the image.
You can also use role="presentation" or role="none" to mark an image as decorative, but an empty alt attribute is simpler and more widely supported.
Examples
Incorrect: Missing Alt Text
The alt attribute is completely absent. A screen reader may announce the file name instead.
<img src="team-photo.jpg">
Incorrect: Unhelpful Alt Text
The alt text contains the file name rather than a meaningful description.
<img src="chart-q3.png" alt="chart-q3.png">
Correct: Descriptive Alt Text Using the alt Attribute
<img src="team-photo.jpg" alt="The engineering team gathered around a whiteboard during a planning session">
Correct: Alt Text Using aria-label
<img src="revenue-chart.png" aria-label="Bar chart showing quarterly revenue increasing from $2M in Q1 to $3.5M in Q4">
Correct: Alt Text Using aria-labelledby
<p id="chart-desc">Bar chart showing quarterly revenue growth from Q1 to Q4 2024.</p>
<img src="revenue-chart.png" aria-labelledby="chart-desc">
Correct: Decorative Image with Empty Alt
This separator line serves no informational purpose and should be hidden from screen readers.
<img src="decorative-line.svg" alt="">
Correct: Decorative Image Using role="presentation"
<img src="decorative-swirl.png" role="presentation">
What This Rule Checks
This axe-core rule (image-alt) inspects every <img> element on the page and verifies that it has an accessible text alternative. Specifically, it checks that each <img> has at least one of the following:
-
A non-empty
altattribute -
An
aria-labelattribute -
An
aria-labelledbyattribute that references a valid element -
An empty
alt=""(orrole="presentation"/role="none") to indicate the image is decorative
If none of these conditions are met, the rule flags the image as a critical accessibility violation.
Why this is a problem
Screen readers process both the alternative text of an image and any visible text surrounding it. When these are identical, users hear something like “Home Page Home Page” — the same phrase spoken back-to-back. This redundancy is disorienting, slows users down, and adds no value. In some cases it can even be misleading, causing users to think there are two separate elements when there is only one.
This issue primarily affects blind and deafblind users who rely on screen readers to interpret page content. It is flagged as a Deque best practice and aligns with the principles behind WCAG Success Criterion 1.1.1 (Non-text Content), which requires that non-text content has a text alternative that serves an equivalent purpose — not a duplicated one.
How to fix it
The fix depends on the context:
-
Decorative or redundant images inside links or buttons: If visible text already describes the purpose, set
alt=""on the image. The image becomes decorative, and the screen reader reads only the visible text. -
Images that add unique information: If the image conveys something the adjacent text does not, write
alttext that describes only the additional information — don’t repeat what’s already there. -
Image buttons (
<input type="image">): Thealtattribute serves as the button’s label. Make sure it clearly describes the action (e.g., “Submit,” “Search”) and does not duplicate text placed next to the button.
Tips for writing good alternative text
- Describe the purpose or action, not the visual appearance of the image.
- Ask yourself: “If I removed this image, what words would I need to convey the same information?”
- Avoid filler words like “image of,” “picture of,” or file names — they add noise without value.
- Keep it concise and meaningful.
Examples
Incorrect: alt text duplicates adjacent link text
The screen reader announces “Home Page Home Page” because both the alt attribute and the visible text say the same thing.
<a href="index.html">
<img src="home-icon.png" alt="Home Page" width="24" height="25">
Home Page
</a>
Correct: empty alt on a redundant image inside a link
Since the visible text “Home Page” already describes the link, the icon is marked as decorative with alt="". The screen reader announces “Home Page” once.
<a href="index.html">
<img src="home-icon.png" alt="" width="24" height="25">
Home Page
</a>
Incorrect: button text duplicated next to an image button
The word “Search” appears both as the alt text on the image button and as adjacent visible text, causing duplication.
<input type="image" src="search.png" alt="Search">
<span>Search</span>
Correct: remove the redundant adjacent text
When using an image button, let the alt attribute be the sole label and remove the duplicate text.
<input type="image" src="search.png" alt="Search">
Correct: image adds unique information alongside text
If the image conveys something the text does not, write alt text that captures only the unique information.
<a href="report.html">
<img src="chart.png" alt="Bar chart showing 40% increase in Q3" width="80" height="60">
Quarterly Report
</a>
Here, the visible text says “Quarterly Report” while the image’s alt text describes what the chart actually shows — no duplication, and both pieces of information are useful.
Every interactive control on a page needs an accessible name so that assistive technologies can announce it to users. For <input> elements with type="button", type="submit", or type="reset", the accessible name typically comes from the value attribute. While <input type="submit"> and <input type="reset"> have default browser-provided labels (“Submit” and “Reset”), <input type="button"> has no default — it renders as an empty, unlabeled button if no name is provided.
This issue critically affects blind and deafblind users who rely on screen readers. When a screen reader encounters an input button without discernible text, it may announce something like “button” with no indication of what the button does. This makes it impossible to navigate forms, submit data, or perform actions confidently. Sighted users can often infer a button’s purpose from visual context, but screen reader users depend entirely on the programmatic accessible name.
Related WCAG Success Criteria
This rule maps to WCAG 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 also relates to Section 508 requirements that every non-text element must have a text equivalent, and EN 301 549 Section 9.4.1.2.
How to Fix It
There are several ways to give an input button a discernible accessible name:
-
valueattribute — The most straightforward approach. Setvalueto descriptive text that communicates the button’s purpose. -
aria-labelattribute — Provides an accessible name directly. Useful when you need the accessible name to differ from the visible text, or whenvaluecannot be used. -
aria-labelledbyattribute — References another element’sidwhose text content becomes the accessible name. The referenced element must exist and contain text. -
titleattribute — Acts as a fallback accessible name. However,titleis less reliable because it is not consistently exposed by all assistive technologies and only appears as a tooltip on hover, making it inaccessible to keyboard and touch users. Prefervalueoraria-labelinstead.
For <input type="submit"> and <input type="reset">, browsers provide default labels, so they pass without an explicit value. However, if you set value="" (an empty string), you override the default and create an empty accessible name, which fails.
Examples
Incorrect: Input button with no accessible name
<form action="/search">
<input type="button" id="go" />
</form>
This button has no value, aria-label, aria-labelledby, or title. A screen reader will announce it as just “button” with no context.
Incorrect: Empty aria-label
<form action="/search">
<input type="button" aria-label="" />
</form>
An empty aria-label does not provide a discernible name.
Incorrect: aria-labelledby referencing a nonexistent or empty element
<form action="/search">
<input type="button" aria-labelledby="nonexistent" />
</form>
<form action="/search">
<input type="button" aria-labelledby="empty-label" />
<div id="empty-label"></div>
</form>
If the referenced element doesn’t exist or has no text content, the button has no accessible name.
Incorrect: Submit or reset with an empty value
<form action="/search">
<input type="submit" value="" />
<input type="reset" value="" />
</form>
Setting value to an empty string overrides the browser’s default label, leaving the button unnamed.
Correct: Using the value attribute
<form action="/search">
<input type="button" value="Search" />
</form>
Correct: Using aria-label
<form action="/search">
<input type="button" aria-label="Search" />
</form>
Correct: Using aria-labelledby
<form action="/search">
<input type="button" aria-labelledby="btn-label" />
<span id="btn-label">Search</span>
</form>
Correct: Submit and reset with default or explicit values
<form action="/search">
<input type="submit" />
<input type="reset" />
<input type="submit" value="Send Form" />
<input type="reset" value="Clear All Fields" />
</form>
The first two rely on the browser’s built-in default labels (“Submit” and “Reset”). The last two provide custom, more descriptive labels through the value attribute. Both approaches are valid, though explicit descriptive labels are generally preferred for clarity.
An <input type="image"> element renders an image that functions as a submit button. Unlike standard <input type="submit"> buttons that display their value attribute as visible text, image buttons rely entirely on their visual appearance to communicate purpose. This means assistive technologies have no way to determine the button’s function unless you explicitly provide alternative text.
This is a critical accessibility barrier for several groups of users. Blind and deafblind users who rely on screen readers will hear something generic like “button” or, worse, the image file name (e.g., “btn_submit_v2.png”), which provides no useful information. Users with low vision who use screen magnifiers in combination with screen readers are similarly affected. Without a clear accessible name, these users cannot confidently interact with forms.
This rule relates to two WCAG success criteria at Level A — the minimum conformance level:
- WCAG 1.1.1 (Non-text Content): All non-text content must have a text alternative that serves the equivalent purpose. An image button is non-text content, so it needs alternative text.
- WCAG 4.1.2 (Name, Role, Value): All user interface components must have a programmatically determinable name. Buttons must expose their name to assistive technologies.
This requirement is also mandated by Section 508, EN 301 549, and the Trusted Tester guidelines.
How to fix it
Provide a non-empty accessible name for every <input type="image"> element using one of these methods:
-
altattribute (preferred): Add analtattribute that describes the button’s action. This is the most straightforward and widely supported approach. -
aria-labelattribute: Set anaria-labelwith a concise description of the button’s action. -
aria-labelledbyattribute: Reference theidof another visible element that describes the button’s action.
Writing effective alternative text
The alternative text for an image button should describe the action the button performs, not the image itself. Ask yourself: “What happens when the user clicks this button?” The answer is your alternative text.
- ✅ “Submit”, “Search”, “Add to cart”, “Log in”
- ❌ “Green arrow icon”, “button.png”, “click here”
Avoid filler words like “image of” or “button” — assistive technologies already announce the element’s role as a button.
Important: Simply placing text next to the image button in the HTML is not sufficient. Screen readers need a programmatically associated label, not visually adjacent text. Nearby text cannot be reliably determined as the button’s label by assistive technologies.
Examples
Incorrect: missing alternative text
The image button has no alt, aria-label, or aria-labelledby attribute. A screen reader might announce this as “submit.png button” or just “button.”
<form action="/search">
<label for="query">Search</label>
<input type="text" id="query" name="q">
<input type="image" src="search-icon.png">
</form>
Incorrect: empty alt attribute
An empty alt attribute tells assistive technologies to ignore the element, effectively hiding the button entirely.
<input type="image" src="search-icon.png" alt="">
Correct: using the alt attribute
<form action="/search">
<label for="query">Search</label>
<input type="text" id="query" name="q">
<input type="image" src="search-icon.png" alt="Search">
</form>
Correct: using aria-label
<input type="image" src="submit-arrow.png" aria-label="Submit order">
Correct: using aria-labelledby
The aria-labelledby attribute points to the id of a visible element. Make sure the referenced element exists and is not hidden with display: none or aria-hidden="true".
<h2 id="checkout-heading">Complete your purchase</h2>
<form action="/checkout">
<!-- form fields -->
<input type="image" src="checkout-btn.png" aria-labelledby="checkout-heading">
</form>
What the rule checks
This rule verifies that every <input type="image"> element has a discoverable accessible name. It fails if the element has no alt, aria-label, or aria-labelledby attribute, or if those attributes are empty or resolve to an empty string.
When an interactive element displays visible text — like a button saying “Submit” — users naturally expect that text to be the element’s name. However, developers sometimes use aria-label or aria-labelledby to set an accessible name that differs from the visible text. This creates a disconnect: what sighted users see and what assistive technologies announce become two different things.
This is a serious accessibility problem that primarily affects speech input users. These users interact with web pages by speaking the names of controls they see on screen. If someone sees a link labeled “Next Page” and says “click Next Page,” but the accessible name is actually “OK,” the speech command fails. The user has no way to know the correct programmatic name, leading to frustration and an inability to use the interface.
This issue also affects screen reader users and users with cognitive disabilities. When a screen reader announces a name that doesn’t match the visible label, it creates confusion — the user may not be sure they’re focused on the right element. For users with cognitive disabilities who rely on consistent, predictable interfaces, this mismatch adds unnecessary complexity.
This rule checks compliance with WCAG 2.1 Success Criterion 2.5.3: Label in Name (Level A). This criterion requires that when a user interface component has a visible text label, the accessible name must contain that visible text. The intent is to ensure that the words users see on screen can be used to interact with the component, regardless of input method.
The rule applies to elements that meet all three conditions:
-
The element has a widget role that supports name from content — specifically:
button,checkbox,gridcell,link,menuitem,menuitemcheckbox,menuitemradio,option,radio,searchbox,switch,tab, ortreeitem. - The element has visible text content.
-
The element has an
aria-labeloraria-labelledbyattribute that overrides the default accessible name.
When evaluating the match, leading and trailing whitespace is ignored, and the comparison is case-insensitive. The complete visible text must either match the accessible name exactly or be fully contained within it.
How to Fix
To resolve this issue:
-
Make the accessible name include the full visible text. If the element’s visible text is “Next Page,” the
aria-labelmust contain “Next Page” — not just part of it, and not something completely different. - Start the accessible name with the visible text. While not strictly required, it’s best practice. This helps speech input users who may only speak the beginning of a label.
-
Consider removing the
aria-labelentirely. If the visible text alone is a sufficient accessible name, you may not need an override at all. The simplest fix is often to let the element derive its name from its content naturally.
Examples
Failing: Accessible name doesn’t match visible text
The visible text says “Next” but the accessible name is “OK”:
<div role="link" aria-label="OK">Next</div>
Failing: Accessible name only contains part of the visible text
The visible text is “The full label” but the accessible name is “the full,” which doesn’t include the complete visible text:
<button aria-label="the full">The full label</button>
Passing: Accessible name matches visible text
The aria-label matches the visible text (trailing whitespace and case differences are ignored):
<div role="link" aria-label="Next Page">next page</div>
Passing: Accessible name contains the full visible text
The visible text “Next Page” is fully contained within the accessible name:
<button aria-label="Next Page in the list">Next Page</button>
Passing: No aria-label override needed
When the visible text is already a good accessible name, simply omit the aria-label:
<button>Next Page</button>
Passing: aria-labelledby references text that includes the visible content
<span id="btn-label">Search the full catalog</span>
<button aria-labelledby="btn-label">Search</button>
Here, the visible text “Search” is contained within the accessible name “Search the full catalog,” so the rule passes. The accessible name begins with the visible label, which is ideal for speech input users.
The title and aria-describedby attributes serve a supplementary role in accessibility — they convey hints, tooltips, or additional descriptions. However, they are not treated as primary labels by accessibility APIs. When a screen reader encounters a form field that has only a title or aria-describedby attribute and no proper label, it may present the text as advisory information rather than as the field’s name. This makes it difficult or impossible for users to understand what input is expected.
This issue primarily affects users who are blind, deafblind, or who rely on assistive technologies such as screen readers. It can also impact users with mobility impairments who use voice control software, since voice control often relies on visible labels to target form elements. A missing visible label also hurts sighted users who may not see a tooltip until they hover over the element.
This rule is classified as a Deque Best Practice and aligns with the broader intent of WCAG Success Criterion 1.3.1 (Info and Relationships), which requires that information and relationships conveyed visually are also available programmatically, and SC 2.4.6 (Labels or Instructions), which requires that labels describe the purpose of form controls. While title and aria-describedby do expose some text, they do not fulfill the requirement for a reliable, programmatically determinable label.
How to Fix It
Every form control needs a proper accessible name. You can provide one using any of these methods:
-
Explicit
<label>element — Associate a<label>with the form control using matchingforandidattributes. This is the most reliable and widely supported approach. -
Implicit
<label>element — Wrap the form control inside a<label>element. This works in most assistive technologies but has inconsistent support in some combinations (e.g., JAWS with<select>menus). -
aria-labelledby— Reference visible text elsewhere on the page by itsid. Useful for complex labeling scenarios. -
aria-label— Apply an invisible label directly to the element. Use this only when a visible label is truly not feasible.
In most cases, an explicit <label> is the best choice. It creates a clear association, provides a larger click/tap target, and is universally supported.
Examples
Incorrect: Using Only title
The title attribute provides a tooltip on hover but does not create a proper accessible label.
<input type="text" title="Enter your email address">
Incorrect: Using Only aria-describedby
The aria-describedby attribute provides a description, not a label. Screen readers may announce this text differently or skip it in certain contexts.
<p id="email-hint">Enter your email address</p>
<input type="text" aria-describedby="email-hint">
Correct: Explicit <label> Element
This is the recommended approach. The for attribute on the <label> matches the id on the input, creating an unambiguous association.
<label for="email">Email Address</label>
<input type="text" id="email" name="email">
You can still use title or aria-describedby for supplementary hints alongside a proper label:
<label for="email">Email Address</label>
<input type="text" id="email" name="email" title="e.g., user@example.com">
Correct: Implicit <label> Element
Wrapping the input inside the <label> creates an implicit association. This works for most assistive technologies but is less reliable than explicit labels.
<label>
Email Address
<input type="text" name="email">
</label>
Correct: Using aria-labelledby
Reference visible text on the page by its id. This is useful when the labeling structure is complex, such as data tables with form controls, or when multiple elements share a label.
<p id="email-label">Email Address</p>
<input type="text" aria-labelledby="email-label">
Correct: Using aria-label
This provides a label that is invisible on screen. Use it only when a visible label cannot be provided, such as a search field with a clearly identifiable search icon and button.
<input type="text" aria-label="Search">
<button type="submit">Search</button>
Note: Because aria-label is not visible, sighted users get no label text. Prefer a visible <label> whenever possible.
Every form element — such as text inputs, checkboxes, radio buttons, and select menus — must have a programmatically associated label so that assistive technologies can identify and announce the purpose of each field. Without these labels, screen reader users cannot determine what information is expected, and users with motor impairments lose the benefit of a larger clickable target area that a properly associated <label> provides.
Why This Matters
When a form element lacks a programmatic label, screen readers either announce it generically (e.g., “edit text” or “checkbox”) or skip meaningful context entirely. This leaves blind, low-vision, and deafblind users unable to understand what data a field expects or what option a checkbox represents. They must guess based on surrounding content, which is unreliable and error-prone.
Labels also benefit users with motor impairments. When a <label> element is properly associated with an input, clicking the label text activates or focuses the associated control. This creates a larger click target, which is especially helpful for people with limited dexterity.
Sighted users often rely on visual proximity to infer a field’s purpose, but assistive technologies need an explicit programmatic relationship between the label text and the form control to convey the same information.
Relevant Standards
This rule maps to WCAG Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that all user interface components have an accessible name that can be programmatically determined. It applies across WCAG 2.0, 2.1, and 2.2, as well as Section 508 (§1194.22(n)), EN 301 549 (9.4.1.2), and Trusted Tester guidelines. The user impact is considered critical.
How to Fix It
There are several ways to associate a label with a form element. Use the approach that best fits your situation, listed here from most recommended to least recommended.
1. Explicit <label> with for and id (Recommended)
The most common and reliable method is to use a <label> element whose for attribute matches the id of the form control. This creates an unambiguous programmatic association.
2. Implicit <label> (Wrapping)
Wrap the form control inside a <label> element. The association is implied by the parent-child relationship.
3. aria-label
Use aria-label when the field’s purpose is conveyed visually through an icon or layout rather than visible text. This creates an invisible label that only screen readers announce.
4. aria-labelledby
Use aria-labelledby when the label text already exists elsewhere on the page, or when you need to combine multiple pieces of text into a single label. Reference one or more element id values.
5. placeholder (Not Recommended)
A placeholder attribute can technically provide an accessible name, but it disappears once the user begins typing, removing the visible label. This creates usability problems for everyone and is not a recommended approach.
General Tips
-
Ensure all
idvalues are unique on the page. - Make label text descriptive and meaningful when read aloud in isolation.
-
Remember that buttons (
<button>,<input type="submit">, etc.) are self-labeling through their text content orvalueattribute and do not need a separate<label>. -
Hidden inputs (
<input type="hidden">) do not need labels since they are not presented to users.
Examples
Incorrect: Input without a label
<div>First name:</div>
<input type="text" id="firstname">
The <div> text is visually near the input, but there is no programmatic relationship. A screen reader will announce only “edit text” with no context.
Correct: Explicit label with for and id
<label for="firstname">First name:</label>
<input type="text" id="firstname">
Correct: Implicit label by wrapping
<label>
First name:
<input type="text">
</label>
Correct: aria-label for visually implied fields
<input type="text" aria-label="Search">
<button type="submit">🔍</button>
Correct: aria-labelledby referencing existing text
<div id="temp-label">Temperature</div>
<div id="high-label">High:</div>
<div id="low-label">Low:</div>
<input type="text" aria-labelledby="temp-label high-label">
<input type="text" aria-labelledby="temp-label low-label">
The first input is announced as “Temperature High:” and the second as “Temperature Low:”, combining the referenced text in order.
Incorrect: Relying only on placeholder
<input type="text" placeholder="Enter your email">
While this technically provides an accessible name, the visible hint disappears when the user starts typing, making it difficult to verify the field’s purpose. Always prefer a persistent visible label.
Correct: Visible label with supplementary placeholder
<label for="email">Email address</label>
<input type="text" id="email" placeholder="name@example.com">
Incorrect: Checkbox without a label
<input type="checkbox" id="terms">
I agree to the terms and conditions
The text is adjacent but not associated with the checkbox.
Correct: Labeled checkbox
<input type="checkbox" id="terms">
<label for="terms">I agree to the terms and conditions</label>
Landmarks provide a structural map of a page’s layout that screen reader users rely on to quickly jump between major sections — the header, navigation, main content, footer, and so on. The banner landmark specifically represents the site-wide header area, typically containing the logo, site title, and primary navigation.
When a banner landmark is nested inside another landmark (for example, inside a <main> element or a <nav> element), screen readers present it as a subordinate section rather than a top-level region of the page. This breaks the expected page structure and makes it significantly harder for blind and deafblind users to orient themselves. Instead of finding the banner at the top level of the landmark tree, they encounter it buried within another region, which undermines the purpose of landmarks entirely.
This rule is a Deque best practice for accessible landmark structure. While it doesn’t map to a specific WCAG success criterion, it directly supports the principles behind WCAG 1.3.1 Info and Relationships (ensuring structural information is programmatically determinable) and WCAG 2.4.1 Bypass Blocks (providing mechanisms to skip repeated content).
How to Fix It
-
Identify the banner landmark — Look for any
<header>element that is a direct child of<body>(which automatically has thebannerrole) or any element withrole="banner". -
Check if it’s nested — Determine whether the banner is contained inside another landmark element such as
<main>,<nav>,<aside>,<section>(with an accessible name), or any element with an ARIA landmark role. -
Move it to the top level — Restructure your HTML so the banner landmark is a direct child of
<body>, outside of any other landmark.
Keep in mind that a <header> element only has the implicit banner role when it is not nested inside <article>, <aside>, <main>, <nav>, or <section>. When nested inside those elements, <header> has no implicit landmark role and won’t trigger this rule. However, if you explicitly add role="banner" to a nested element, or if your page structure inadvertently places the top-level <header> inside another landmark, the issue will be flagged.
Examples
Incorrect: Banner Nested Inside Main Landmark
In this example, the <header> with role="banner" is placed inside <main>, making it a nested landmark.
<body>
<main>
<header role="banner">
<h1>My Website</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
<p>Main content goes here.</p>
</main>
</body>
Incorrect: Banner Inside a Navigation Landmark
<body>
<nav aria-label="Site navigation">
<div role="banner">
<img src="logo.png" alt="Company Logo">
</div>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<main>
<p>Main content goes here.</p>
</main>
</body>
Correct: Banner as a Top-Level Landmark
The <header> is a direct child of <body>, making it a proper top-level banner landmark.
<body>
<header>
<h1>My Website</h1>
<nav aria-label="Primary">
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
<main>
<p>Main content goes here.</p>
</main>
<footer>
<p>© 2024 My Website</p>
</footer>
</body>
Correct: Using role="banner" at the Top Level
<body>
<div role="banner">
<h1>My Website</h1>
<nav aria-label="Primary">
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</div>
<main>
<p>Main content goes here.</p>
</main>
</body>
Note that using the native <header> element is preferred over role="banner" on a <div>, since native HTML semantics are more robust and widely supported.
Landmark regions are structural areas of a page — like <main>, <nav>, <header>, <footer>, and <aside> — that help assistive technology users understand the layout and quickly navigate between sections. Screen readers provide shortcut keys that let users jump directly to these landmarks, making it easy to skip to the content they need.
The <aside> element (or role="complementary") represents content that supplements the main content, such as sidebars, related links, or additional context. When an <aside> is nested inside another landmark like <main> or <nav>, screen readers may not expose it as a top-level landmark. This means users who rely on landmark navigation may not be able to discover or jump to that complementary content at all, effectively hiding it from their navigation flow.
This issue primarily affects screen reader users, who depend on landmarks as a primary way to orient themselves and move through a page. When landmarks are improperly nested, the page structure becomes confusing or incomplete, reducing the efficiency and usability of assistive technology.
This rule relates to WCAG 2.1 Success Criterion 1.3.1 (Info and Relationships), which requires that information, structure, and relationships conveyed visually are also available programmatically. It also supports WCAG 2.1 Success Criterion 4.1.2 (Name, Role, Value) and the general best practice of using landmarks correctly so that assistive technologies can present content structure accurately.
How to Fix It
-
Move
<aside>elements to the top level of the document body, outside of<main>,<nav>,<header>,<footer>, or any other landmark. -
Check for
role="complementary"on any elements and ensure they are also placed at the top level, not nested inside another landmark. -
If the content truly belongs inside another landmark and is not meant to be independently navigable, consider whether it actually needs to be an
<aside>at all. A simple<div>or<section>without a landmark role may be more appropriate.
Examples
Incorrect: <aside> nested inside <main>
<main>
<h1>Article Title</h1>
<p>Main article content goes here.</p>
<aside>
<h2>Related Links</h2>
<ul>
<li><a href="/topic-a">Topic A</a></li>
<li><a href="/topic-b">Topic B</a></li>
</ul>
</aside>
</main>
In this example, the <aside> is inside <main>, so screen readers may not list it as a top-level landmark. Users navigating by landmarks could miss the related links entirely.
Correct: <aside> at the top level alongside <main>
<main>
<h1>Article Title</h1>
<p>Main article content goes here.</p>
</main>
<aside>
<h2>Related Links</h2>
<ul>
<li><a href="/topic-a">Topic A</a></li>
<li><a href="/topic-b">Topic B</a></li>
</ul>
</aside>
Now the <aside> is a sibling of <main>, making it a top-level landmark that screen reader users can easily discover and navigate to.
Incorrect: role="complementary" nested inside <nav>
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
<div role="complementary">
<p>Navigation tip: Use keyboard shortcuts to browse faster.</p>
</div>
</nav>
Correct: role="complementary" moved to the top level
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<div role="complementary" aria-label="Navigation tips">
<p>Navigation tip: Use keyboard shortcuts to browse faster.</p>
</div>
By keeping complementary landmarks at the top level of the page, you ensure all users — including those using assistive technology — can discover and navigate to every section of your content.
Landmarks are one of the primary ways screen reader users orient themselves on a page and navigate between major sections. The contentinfo landmark is specifically intended to hold site-wide footer information — copyright notices, privacy policies, contact links, and similar content. Screen readers like JAWS, NVDA, and VoiceOver provide shortcut keys that let users jump directly to landmarks by role.
When a contentinfo landmark is nested inside another landmark (such as main or navigation), it no longer appears as a top-level landmark in the screen reader’s landmark list. This means blind and deafblind users may not be able to find the footer information efficiently, or they may encounter it unexpectedly while navigating a different section of the page. The organizational benefit of landmarks is undermined when they are improperly nested.
This rule is a Deque best practice aligned with the broader principles of proper landmark usage recommended by the W3C’s ARIA Landmarks specification. While not mapped to a specific WCAG success criterion, correct landmark structure supports WCAG 1.3.1 (Info and Relationships), WCAG 2.4.1 (Bypass Blocks), and the overall goal of providing a clear, navigable document structure.
How to Fix It
-
Locate your
contentinfolandmark. This is either a<footer>element that is a direct child of<body>(which implicitly hasrole="contentinfo") or any element with an explicitrole="contentinfo". -
Check its position in the DOM. If the
contentinfolandmark is nested inside a<main>,<nav>,<aside>,<section>,<header>, or any element with a landmark role, it needs to be moved. -
Move it to the top level. Restructure your HTML so the
contentinfolandmark is a direct child of<body>, outside all other landmark regions.
Note that a <footer> element only has the implicit contentinfo role when it is scoped to the <body>. A <footer> nested inside an <article>, <section>, <aside>, <nav>, or <main> does not have the contentinfo role — it becomes a generic element. This means the issue typically arises when you explicitly add role="contentinfo" to a nested element, or when your page structure accidentally places the site-wide <footer> inside another landmark.
Examples
Incorrect: contentinfo Landmark Nested Inside main
<body>
<main>
<h1>Welcome</h1>
<p>Page content goes here.</p>
<footer>
<p>© 2024 Example Company</p>
</footer>
</main>
</body>
In this example, the <footer> is inside <main>, so it does not receive the implicit contentinfo role. If a developer tries to fix that by adding role="contentinfo" explicitly, the landmark becomes nested inside main, which triggers this rule:
<body>
<main>
<h1>Welcome</h1>
<p>Page content goes here.</p>
<div role="contentinfo">
<p>© 2024 Example Company</p>
</div>
</main>
</body>
Correct: contentinfo Landmark at the Top Level
<body>
<main>
<h1>Welcome</h1>
<p>Page content goes here.</p>
</main>
<footer>
<p>© 2024 Example Company</p>
</footer>
</body>
Here, the <footer> is a direct child of <body> and sits outside the <main> landmark. It automatically receives the implicit contentinfo role, and screen reader users can jump directly to it using landmark navigation.
Incorrect: Explicit role="contentinfo" Inside a region
<body>
<main>
<h1>Dashboard</h1>
</main>
<section aria-label="Additional info">
<footer role="contentinfo">
<p>Privacy Policy | Terms of Service</p>
</footer>
</section>
</body>
Correct: contentinfo Moved Outside the region
<body>
<main>
<h1>Dashboard</h1>
</main>
<footer>
<p>Privacy Policy | Terms of Service</p>
</footer>
</body>
The contentinfo landmark is now at the top level of the document, making it immediately discoverable through screen reader landmark navigation.
The <main> element (or role="main") represents the dominant content of a page — the content that is directly related to the central topic or functionality. Screen reader users rely on landmark navigation to jump quickly between major sections of a page, such as the banner, navigation, main content, and footer. When the <main> landmark is nested inside another landmark, this hierarchy becomes confusing. A screen reader might present it as a subsection of, say, the navigation or banner, making it harder for users to understand the page structure and locate the primary content efficiently.
This rule primarily affects users who are blind, deafblind, or have mobility impairments and depend on assistive technologies to navigate by landmarks. When landmarks are properly structured as flat, top-level regions, users can cycle through them predictably and efficiently.
Why landmark hierarchy matters
The HTML Living Standard specifies that a hierarchically correct <main> element is one whose ancestor elements are limited to <html>, <body>, <div>, <form> without an accessible name, and autonomous custom elements. This means <main> should never appear inside <header>, <nav>, <aside>, <footer>, or any element with a landmark role.
Think of landmarks as the top-level sections of your page. They should be siblings, not nested within each other. A well-structured page has a clear, flat hierarchy of landmarks:
-
Banner (
<header>orrole="banner") — site-wide header content -
Navigation (
<nav>orrole="navigation") — navigation links -
Main (
<main>orrole="main") — primary page content -
Contentinfo (
<footer>orrole="contentinfo") — footer content
This rule is classified as a Deque best practice and supports the broader principle of providing clear, navigable page structure for assistive technology users.
How to fix it
-
Move
<main>out of any enclosing landmark. If your<main>element is nested inside a<header>,<nav>,<aside>,<footer>, or any element with an ARIA landmark role, restructure your markup so<main>is a direct child of<body>(or only wrapped in non-landmark elements like<div>). - Ensure landmarks are siblings, not nested. The primary landmarks — banner, navigation, main, and contentinfo — should sit at the same level in the DOM.
-
Use both HTML5 elements and ARIA roles for maximum compatibility. While modern browsers and screen readers handle HTML5 landmark elements well, adding the corresponding ARIA role (e.g.,
<main role="main">) provides a fallback for older assistive technologies.
Examples
Incorrect: <main> nested inside another landmark
In this example, the <main> element is nested inside the <nav> landmark, which breaks the expected landmark hierarchy.
<header role="banner">
<p>Company Logo</p>
</header>
<nav role="navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
<main role="main">
<p>This is the primary content, incorrectly nested inside nav.</p>
</main>
</nav>
<footer role="contentinfo">
<p>Copyright 2024</p>
</footer>
Incorrect: <main> nested inside <aside>
<aside role="complementary">
<main role="main">
<p>Main content should not be inside a complementary landmark.</p>
</main>
</aside>
Correct: <main> as a top-level landmark
All landmarks are siblings at the top level of the document. The <main> element is not contained within any other landmark.
<header role="banner">
<p>Company Logo</p>
</header>
<nav role="navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<main role="main">
<p>This is the primary content of the page.</p>
</main>
<footer role="contentinfo">
<p>Copyright 2024</p>
</footer>
Correct: <main> wrapped in a non-landmark <div>
Wrapping <main> in a <div> is acceptable because <div> is not a landmark element.
<header role="banner">
<p>Company Logo</p>
</header>
<div class="content-wrapper">
<main role="main">
<p>This is the primary content of the page.</p>
</main>
</div>
<footer role="contentinfo">
<p>Copyright 2024</p>
</footer>
Ready to validate your sites?
Start your free trial today.