Accessibility Guides for Deque Best Practice
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 accesskey attribute provides a keyboard shortcut that lets users quickly activate or move focus to a specific element, typically by pressing the designated key in combination with a modifier key (such as Alt on Windows or Control + Option on macOS). When multiple elements share the same accesskey value, browsers handle the conflict inconsistently — some will focus the first matching element, others will cycle through matches, and some may ignore the duplicates entirely. This unpredictability undermines the purpose of access keys and creates a confusing experience.
This issue primarily affects users who rely on the keyboard to navigate, including people who are blind and use screen readers, users with low vision who cannot easily track a mouse pointer, and users with motor disabilities who depend on keyboards or keyboard-emulating input devices. For these users, a duplicated access key can mean they can never reach the intended element, or they reach the wrong one without realizing it.
This rule is classified as a Deque Best Practice. While it doesn’t map directly to a specific WCAG success criterion, it supports the broader principles behind WCAG 2.1.1 Keyboard (all functionality must be operable via keyboard) and WCAG 4.1.2 Name, Role, Value (user interface components must behave predictably). Duplicate access keys undermine keyboard operability by introducing unreliable shortcuts.
How to fix it
-
Find all elements on the page that use the
accesskeyattribute. -
Identify duplicates — any
accesskeyvalue that appears on more than one element. - Assign a unique value to each element so no two share the same key.
-
Avoid conflicts with default browser and screen reader shortcuts. For example, many browsers reserve
Alt + Dfor the address bar andAlt + Ffor the File menu.
A note of caution about accesskey
While fixing duplicates is important, be aware that the accesskey attribute has significant limitations in practice:
- Inconsistent browser support: Not all browsers implement access keys the same way, and modifier key combinations vary across operating systems.
- Conflict risk: Developer-defined access keys can override default browser or assistive technology shortcuts, potentially breaking expected functionality.
- Discoverability: Access keys are invisible to most users unless explicitly documented on the page.
-
Localization issues: A key that makes sense as a mnemonic in one language (e.g.,
sfor “Search”) may not work in another.
For these reasons, many accessibility experts recommend avoiding accesskey altogether and instead relying on well-structured headings, landmarks, and skip links for efficient keyboard navigation.
Examples
Incorrect: duplicate accesskey values
Both links use accesskey="g", so the browser cannot determine which element to activate.
<a href="https://google.com" accesskey="g">Link to Google</a>
<a href="https://github.com" accesskey="g">Link to GitHub</a>
Correct: unique accesskey values
Each link has a distinct accesskey value, so keyboard shortcuts work as expected.
<a href="https://google.com" accesskey="g">Link to Google</a>
<a href="https://github.com" accesskey="h">Link to GitHub</a>
Correct: removing accesskey in favor of better patterns
If access keys aren’t essential, consider removing them and providing clear navigation structure instead.
<nav aria-label="Main navigation">
<a href="https://google.com">Link to Google</a>
<a href="https://github.com">Link to GitHub</a>
</nav>
HTML elements come with built-in semantics — a <button> is inherently a button, a <ul> is inherently a list, and so on. When you assign a WAI-ARIA role to an element, you’re overriding those built-in semantics and telling assistive technologies to treat the element differently. However, not all overrides make sense. Some combinations conflict with the element’s native behavior, its expected interaction patterns, or the browser’s internal handling of the element. For example, assigning role="button" to a <ul> element creates a contradiction: the browser still treats it as a list structurally, but screen readers announce it as a button, resulting in confusing and unpredictable behavior.
This issue primarily affects users who rely on screen readers and other assistive technologies. These tools depend on accurate role information to communicate what an element is, how it behaves, and how to interact with it. When role assignments conflict with the underlying HTML element, screen readers may announce the wrong type of control, skip content entirely, or present a user interface that doesn’t match what sighted users see. Users who are blind, deafblind, or who use assistive technologies for mobility impairments are all affected.
While this rule is classified as a Deque best practice rather than a direct WCAG failure, it aligns with the principles behind WCAG 4.1.2 Name, Role, Value, which requires that user interface components expose their role correctly to assistive technologies. Mismatched roles undermine this requirement. At best, an invalid element-role combination has no effect; at worst, it can disable accessibility for entire sections of a page.
How to Fix
-
Check the ARIA in HTML specification. The ARIA in HTML spec defines which
rolevalues are allowed for each HTML element. Before assigning a role, verify that the combination is permitted. -
Use the right element for the job. Often, the best fix is to use a native HTML element that already has the semantics you need, rather than overriding a different element with a
role. For example, use a<button>for button behavior instead of addingrole="button"to a<div>. - Restructure when necessary. If you need a specific role, find an element that is allowed to carry it. This might mean changing your markup structure slightly.
-
Don’t use abstract roles. Roles like
widget,landmark,roletype,structure, andcommandare abstract and must never be used directly in content. They exist only as conceptual categories in the ARIA taxonomy.
Examples
Incorrect: Role not appropriate for the element
A <ul> element assigned role="button" — lists cannot be buttons:
<ul role="button">Name</ul>
A <button> element assigned role="heading" — buttons should not be headings:
<button role="heading" aria-level="2">Welcome</button>
In both cases, the assigned role conflicts with the element’s native semantics and expected behavior, causing assistive technologies to report inaccurate information.
Correct: Role appropriate for the element
A <ul> element assigned role="menu" — this is an allowed role for <ul>, and the child elements use compatible roles:
<ul role="menu">
<li role="none">
<button role="menuitem">Start</button>
</li>
<li role="none">
<button role="menuitem">Stop</button>
</li>
</ul>
Here, role="menu" is a permitted override for <ul>, role="none" removes the <li> list-item semantics (which aren’t meaningful in a menu context), and role="menuitem" is allowed on <button> elements.
Correct: Using native HTML elements instead of role overrides
Rather than forcing mismatched roles, use elements that already have the semantics you need:
<button type="button">Name</button>
<h2>Welcome</h2>
Native HTML elements provide built-in keyboard support, focus management, and correct semantics without any ARIA needed. This is almost always the simplest and most robust approach.
When a dialog appears on screen, assistive technologies announce it to the user along with its accessible name. This name gives essential context — it tells the user what the dialog is about, such as “Confirm deletion” or “Sign in to your account.” Without an accessible name, screen reader users hear that a dialog has opened but have no way to understand its purpose, which can be disorienting and make the interface difficult to use.
This issue primarily affects users who are blind or have low vision and rely on screen readers, as well as users with mobility impairments who navigate with assistive technology. It is flagged as a serious issue by the axe accessibility engine and aligns with Deque’s accessibility best practices.
How to Fix It
Ensure every element with role="dialog" or role="alertdialog" has an accessible name using one of these methods:
-
aria-label— Provide a concise, descriptive name directly on the dialog element. -
aria-labelledby— Reference theidof a visible element (typically a heading) inside the dialog that serves as its title. -
title— Use the HTMLtitleattribute as a fallback, thougharia-labeloraria-labelledbyare preferred.
Whichever method you choose, make sure the name clearly describes the dialog’s purpose. Avoid empty strings or references to elements that don’t exist or have no text content.
Examples
Incorrect: Dialog with No Accessible Name
The dialog has no aria-label, aria-labelledby, or title, so screen readers cannot announce its purpose.
<div role="dialog">
<h2>Unsaved Changes</h2>
<p>You have unsaved changes. Do you want to save before leaving?</p>
<button>Save</button>
<button>Discard</button>
</div>
Incorrect: Empty aria-label
An empty aria-label provides no useful information to assistive technologies.
<div role="alertdialog" aria-label="">
<p>An error occurred while saving your file.</p>
<button>OK</button>
</div>
Incorrect: aria-labelledby Pointing to a Nonexistent or Empty Element
If the referenced element doesn’t exist or contains no text, the dialog still has no accessible name.
<div role="dialog" aria-labelledby="dialog-title">
<p>Please confirm your selection.</p>
<button>Confirm</button>
</div>
<!-- No element with id="dialog-title" exists -->
<div role="dialog" aria-labelledby="dialog-title">
<span id="dialog-title"></span>
<p>Please confirm your selection.</p>
<button>Confirm</button>
</div>
Correct: Using aria-labelledby to Reference a Heading
The aria-labelledby attribute points to the dialog’s visible heading, giving it a clear accessible name.
<div role="dialog" aria-labelledby="dialog-title">
<h2 id="dialog-title">Unsaved Changes</h2>
<p>You have unsaved changes. Do you want to save before leaving?</p>
<button>Save</button>
<button>Discard</button>
</div>
Correct: Using aria-label
The aria-label attribute provides a concise name directly on the dialog.
<div role="alertdialog" aria-label="File save error">
<p>An error occurred while saving your file. Please try again.</p>
<button>Retry</button>
<button>Cancel</button>
</div>
Correct: Using the title Attribute
The title attribute works as a fallback naming mechanism, though aria-label or aria-labelledby are generally preferred because title has inconsistent support across assistive technologies.
<div role="dialog" title="Export Settings">
<p>Choose a format for your export.</p>
<select>
<option>PDF</option>
<option>CSV</option>
</select>
<button>Export</button>
<button>Cancel</button>
</div>
The role="text" attribute is a workaround for a specific screen reader behavior. When a text node is split by inline markup — for example, <h1>Good morning, <span>friend</span></h1> — VoiceOver on macOS and iOS may announce this as two separate phrases instead of reading it as one continuous string. Wrapping the content in an element with role="text" tells the screen reader to treat everything inside as a single text node, which produces a smoother reading experience.
However, role="text" comes with an important side effect: it overrides the semantic role of the container element and all of its descendants. Every child element is effectively treated as plain text. This means that if any descendant is focusable — such as an <a>, <button>, or <input> — the screen reader can no longer recognize it for what it is. The element remains in the tab order because the browser still considers it focusable, but VoiceOver will not announce its name, role, or value. The result is a “ghost” tab stop: the user presses Tab, focus moves to an element, but nothing is announced. This is a serious problem for blind users and keyboard-only users who rely on focus announcements to navigate and interact with a page.
Who is affected
-
Blind users using screen readers like VoiceOver cannot identify focusable elements trapped inside a
role="text"container. They encounter empty tab stops with no announcement. - Keyboard-only users experience confusing focus behavior — tabbing lands on elements that provide no information about what they are or what they do.
- Users with low vision who use screen readers in combination with magnification may also be impacted.
Related standards
This rule is classified as a Deque Best Practice. While it does not map directly to a specific WCAG success criterion, it relates closely to:
-
WCAG 4.1.2 (Name, Role, Value): All user interface components must have an accessible name and role that can be programmatically determined. A focusable element inside
role="text"loses its role and may lose its accessible name. - WCAG 2.4.7 (Focus Visible): Users must be able to understand where focus is. An empty tab stop with no screen reader announcement undermines this.
- WCAG 2.1.1 (Keyboard): All functionality must be operable through a keyboard. If a user cannot identify a focusable element, they effectively cannot use it.
How to fix it
-
Move focusable elements outside the
role="text"container so they retain their semantic roles and are properly announced. -
Only wrap non-interactive content with
role="text". Use it exclusively for its intended purpose: merging split text nodes into a single phrase for screen readers. -
Remove
role="text"entirely if the content includes interactive elements and you cannot restructure the markup. It is better to have VoiceOver announce text in separate phrases than to create inaccessible interactive elements.
Examples
Incorrect: focusable link inside role="text"
The link is inside the role="text" container, so VoiceOver treats it as plain text. The user can Tab to the link, but nothing is announced.
<p role="text">
For more details, visit
<a href="/about">our about page</a>.
</p>
Incorrect: focusable button inside role="text"
<div role="text">
Your session is about to expire.
<button>Extend session</button>
</div>
Correct: role="text" wraps only non-interactive content
The role="text" is applied to a <span> that contains no focusable elements, while the link sits outside it.
<p>
<span role="text">Good morning, <span>friend</span></span>.
Visit <a href="/dashboard">your dashboard</a> to get started.
</p>
Correct: role="text" on a heading with split text nodes
This is the primary use case for role="text" — ensuring VoiceOver reads a heading as a single phrase rather than separate fragments.
<h1>
<span role="text">Hello <br/>World</span>
</h1>
Correct: restructured to avoid the problem entirely
If you cannot separate interactive and non-interactive content, remove role="text" altogether.
<p>
For more details, visit
<a href="/about">our about page</a>.
</p>
The key principle is straightforward: role="text" is meant for presentation of static text only. Never place links, buttons, inputs, or any other focusable element inside it.
The treeitem role represents an item within a hierarchical tree widget, commonly used for file explorers, nested navigation menus, or collapsible category lists. When a treeitem lacks an accessible name, screen readers announce something like “tree item” with no further context, making it impossible for users who rely on assistive technology to distinguish one item from another or understand the tree’s structure.
This issue primarily affects screen reader users, but it can also impact users of voice control software who need to reference elements by name to interact with them.
This rule relates to WCAG 2.1 Success Criterion 4.1.2 (Name, Role, Value), which requires that all user interface components have a name that can be programmatically determined. It also supports Success Criterion 1.3.1 (Info and Relationships), ensuring that information conveyed through structure is available to all users.
How to Fix It
You can give a treeitem an accessible name in several ways:
- Inner text content — Place descriptive text directly inside the element.
-
aria-label— Add anaria-labelattribute with a descriptive string. -
aria-labelledby— Reference another element’sidthat contains the label text. -
titleattribute — Use thetitleattribute as a last resort (less reliable across assistive technologies).
The accessible name should be concise and clearly describe the item’s purpose or content.
Examples
Incorrect: treeitem with no accessible name
<ul role="tree">
<li role="treeitem"></li>
<li role="treeitem"></li>
</ul>
Screen readers announce these items as “tree item” with no distinguishing label.
Incorrect: treeitem with only a non-text child and no label
<ul role="tree">
<li role="treeitem">
<span class="icon-folder"></span>
</li>
</ul>
If the span renders only a CSS icon and contains no text, the treeitem still has no accessible name.
Correct: treeitem with visible text content
<ul role="tree">
<li role="treeitem">Documents</li>
<li role="treeitem">Photos</li>
<li role="treeitem">Music</li>
</ul>
Correct: treeitem with aria-label
<ul role="tree">
<li role="treeitem" aria-label="Documents">
<span class="icon-folder"></span>
</li>
<li role="treeitem" aria-label="Photos">
<span class="icon-folder"></span>
</li>
</ul>
Correct: treeitem with aria-labelledby
<ul role="tree">
<li role="treeitem" aria-labelledby="item1-label">
<span class="icon-folder"></span>
<span id="item1-label">Documents</span>
</li>
<li role="treeitem" aria-labelledby="item2-label">
<span class="icon-folder"></span>
<span id="item2-label">Photos</span>
</li>
</ul>
Correct: Nested treeitem elements with accessible names
<ul role="tree">
<li role="treeitem" aria-expanded="true">
Documents
<ul role="group">
<li role="treeitem">Resume.pdf</li>
<li role="treeitem">Cover Letter.docx</li>
</ul>
</li>
<li role="treeitem" aria-expanded="false">
Photos
</li>
</ul>
Every treeitem in the tree — including nested ones — must have an accessible name. When building tree widgets, also ensure proper keyboard interaction (arrow keys for navigation, Enter/Space for activation) and correct use of aria-expanded on parent items that contain child groups.
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.
Table headers play a critical role in making data tables understandable. They label rows and columns so users can interpret the data within each cell. When a table header is empty — containing no visible text — it creates confusion for everyone. Sighted users see a blank cell where a label should be, and screen reader users hear nothing meaningful when navigating to that header, making it difficult or impossible to understand the relationship between the header and its associated data cells.
Screen readers rely on table headers to announce context as users navigate through cells. For example, when a screen reader user moves between cells in a column, the column header is announced to remind them which column they’re in. If that header is empty, the user loses that context entirely.
This rule is identified as empty-table-header in axe-core and is classified as a Deque Best Practice. It primarily affects users who are blind or have low vision and rely on screen readers, but it also impacts sighted users who depend on clear visual labels to understand table data.
Why visible text matters
It’s important to note that this rule specifically checks for visible text. Using only aria-label or aria-labelledby on an empty <th> does not satisfy this rule. While those attributes may provide a name for assistive technology, they don’t help sighted users who also need to see the header text. The goal is to ensure that the header’s purpose is communicated visually and programmatically.
How to fix it
-
Add visible text to every
<th>element so it clearly describes the row or column it represents. -
If the cell isn’t a header, change it from a
<th>to a<td>. This is common for empty corner cells in tables where row and column headers intersect. -
Avoid using only ARIA attributes like
aria-labelon an otherwise empty header. Always include visible text content.
Examples
Incorrect: empty table header
The <th> element has no text content, leaving both sighted and screen reader users without context.
<table>
<tr>
<th></th>
<th>Q1</th>
<th>Q2</th>
</tr>
<tr>
<th>Revenue</th>
<td>$100k</td>
<td>$150k</td>
</tr>
</table>
Incorrect: table header with only aria-label
While aria-label provides a name for assistive technology, there is no visible text for sighted users.
<table>
<tr>
<th aria-label="Student Name"></th>
<th aria-label="Grade"></th>
</tr>
<tr>
<td>Alice</td>
<td>A</td>
</tr>
</table>
Correct: table headers with visible text
Each <th> contains visible text that clearly describes its column.
<table>
<tr>
<th>Student Name</th>
<th>Grade</th>
</tr>
<tr>
<td>Alice</td>
<td>A</td>
</tr>
</table>
Correct: using <td> for a non-header cell
When the top-left corner cell of a table isn’t functioning as a header, use <td> instead of an empty <th>.
<table>
<tr>
<td></td>
<th>Q1</th>
<th>Q2</th>
</tr>
<tr>
<th>Revenue</th>
<td>$100k</td>
<td>$150k</td>
</tr>
</table>
Correct: visually hidden text for special cases
In rare cases where a header needs visible text for assistive technology but the visual design calls for no visible label, you can use a CSS visually-hidden technique. Note that this is a compromise — visible text is always preferred.
<table>
<tr>
<th>
<span class="visually-hidden">Category</span>
</th>
<th>Q1</th>
<th>Q2</th>
</tr>
<tr>
<th>Revenue</th>
<td>$100k</td>
<td>$150k</td>
</tr>
</table>
.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;
}
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.
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.
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>
This rule is an informational check rather than a pass/fail test. It identifies elements whose content is hidden from both sighted users and assistive technologies, meaning automated tools like axe cannot inspect that content for issues. The goal is to ensure you don’t overlook potentially inaccessible content simply because it’s currently not visible.
Why this matters
When content is hidden using CSS properties like display: none or visibility: hidden, it is removed from the accessibility tree entirely. This means screen readers cannot access it, and automated testing tools cannot evaluate it. If that hidden content is later revealed — through user interaction, JavaScript toggling, or media queries — it must be fully accessible when it becomes visible.
Several groups of users can be affected by inaccessible hidden content:
- Screen reader users may encounter content that lacks proper labels, headings, or semantic structure once it’s revealed.
- Keyboard-only users may find that revealed content contains focus traps or elements that aren’t keyboard operable.
- Users with low vision or color blindness may encounter contrast or styling issues in content that was never tested because it was hidden during analysis.
If there’s a compelling reason to hide content from sighted users, there’s usually an equally compelling reason to hide it from screen reader users too. Conversely, when content is available to sighted users, it should also be available to assistive technology users.
This rule aligns with Deque best practices for thorough accessibility testing. While it doesn’t map to a specific WCAG success criterion, failing to review hidden content could mask violations of criteria like 1.1.1 Non-text Content, 1.3.1 Info and Relationships, 2.1.1 Keyboard, 4.1.2 Name, Role, Value, and many others.
How to fix it
When axe flags hidden content, you need to:
- Identify the hidden elements reported by the rule.
- Trigger their display — interact with the page to make the content visible (e.g., open a modal, expand an accordion, hover over a tooltip).
- Run accessibility checks again on the now-visible content.
- Fix any issues found in that content, just as you would for any visible element.
-
Verify hiding technique is appropriate — make sure content hidden with
display: noneorvisibility: hiddenis truly meant to be hidden from all users, including assistive technology users.
If you intend content to be visually hidden but still accessible to screen readers, use a visually-hidden CSS technique instead of display: none or visibility: hidden.
Examples
Hidden content that cannot be analyzed
This content is completely hidden from all users and automated tools:
<div style="display: none;">
<img src="chart.png">
<p>Quarterly revenue increased by 15%.</p>
</div>
The img element inside may be missing an alt attribute, but axe cannot detect this because the entire div is hidden. You must reveal this content and test it separately.
Hidden content revealed for testing and fixed
Once the content is made visible, you can identify and fix issues:
<div style="display: block;">
<img src="chart.png" alt="Bar chart showing quarterly revenue increased by 15%">
<p>Quarterly revenue increased by 15%.</p>
</div>
Using visibility: hidden
<nav style="visibility: hidden;">
<a href="/home">Home</a>
<a href="/about">About</a>
</nav>
This navigation is hidden from everyone. If it becomes visible through a JavaScript interaction, ensure it is tested in that visible state.
Visually hidden but accessible to screen readers
If you want content hidden visually but still available to assistive technologies, don’t use display: none or visibility: hidden. Use a visually-hidden class instead:
<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>
<button>
<!-- icon -->
</svg>
<span class="visually-hidden">Close menu</span>
</button>
This approach keeps the text accessible to screen readers while hiding it visually. Axe can still analyze this content because it remains in the accessibility tree.
Interactive content that toggles visibility
When content is toggled dynamically, make sure both states are tested:
<button aria-expanded="false" aria-controls="panel1">Show details</button>
<div id="panel1" style="display: none;">
<p>Additional product details go here.</p>
</div>
<script>
const button = document.querySelector('button');
const panel = document.getElementById('panel1');
button.addEventListener('click', () => {
const expanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', String(!expanded));
panel.style.display = expanded ? 'none' : 'block';
});
</script>
To fully test this, click the button to reveal the panel content, then run your accessibility analysis again to check the revealed content for any violations.
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.
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.
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>
Landmarks are structural markers that allow assistive technology users to quickly navigate to key sections of a page. Screen readers like JAWS, NVDA, and VoiceOver provide shortcut keys that let users jump directly to landmarks such as the banner, navigation, main content, and footer. When a page has more than one banner landmark, users encounter ambiguity — they can’t tell which banner is the primary site-wide header, defeating the purpose of landmark navigation.
This issue primarily affects blind users and deafblind users who rely on screen readers, as well as sighted keyboard users who may use browser extensions for landmark navigation. When landmarks are duplicated, these users must spend extra time sorting through repeated or redundant regions to find the content they need.
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 goals of WCAG 1.3.1 Info and Relationships and WCAG 2.4.1 Bypass Blocks by ensuring that the page structure is logical and navigable.
Understanding the Banner Landmark
A banner landmark represents the site-wide header — the region that typically contains the site logo, site name, global navigation, and search. There are two ways a banner landmark is created:
-
A
<header>element that is a direct child of<body>(not nested inside<main>,<article>,<section>,<aside>, or<nav>) automatically has an implicitrole="banner". -
Any element with
role="banner"explicitly assigned.
Although the HTML5 spec allows multiple <header> elements on a page, only a top-level <header> maps to the banner landmark role. A <header> nested inside an <article> or <section> does not become a banner landmark — it’s simply a header for that section. The confusion arises when developers place multiple <header> elements directly under <body>, or mix explicit role="banner" with a top-level <header>.
How to Fix It
-
Identify all banner landmarks on your page. Look for top-level
<header>elements and any element withrole="banner". - Keep only one as the primary site-wide banner. This should be the header that contains your site logo, site name, or global navigation.
-
Remove or restructure any additional banner landmarks:
-
If you have extra
<header>elements at the top level, nest them inside a sectioning element like<section>or<article>so they lose their implicit banner role. -
If you have extra
role="banner"attributes, remove them.
-
If you have extra
-
Don’t use
role="banner"on a<header>that is already a direct child of<body>— it’s redundant. Conversely, don’t addrole="banner"to multiple elements.
Examples
Incorrect: Multiple Banner Landmarks
<body>
<header>
<h1>My Website</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
<header>
<p>Welcome to the promotional section</p>
</header>
<main>
<p>Page content here.</p>
</main>
</body>
Both <header> elements are direct children of <body>, so both become banner landmarks. Screen readers will report two banners.
Incorrect: Mixing role="banner" with a Top-Level <header>
<body>
<header>
<h1>My Website</h1>
</header>
<div role="banner">
<p>Site-wide announcement</p>
</div>
<main>
<p>Page content here.</p>
</main>
</body>
The <header> implicitly has role="banner", and the <div> explicitly has role="banner", resulting in two banner landmarks.
Correct: Single Banner Landmark
<body>
<header>
<h1>My Website</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
<main>
<article>
<header>
<h2>Article Title</h2>
<p>Published on January 1, 2025</p>
</header>
<p>Article content here.</p>
</article>
</main>
</body>
Only the first <header> is a direct child of <body> and maps to the banner landmark. The second <header> is nested inside <article>, so it does not create a banner landmark.
Correct: Using role="banner" on a Single Element
<body>
<div role="banner">
<h1>My Website</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</div>
<main>
<p>Page content here.</p>
</main>
</body>
Only one element carries the banner role, so screen readers correctly identify a single banner landmark.
Landmarks are one of the primary ways screen reader users orient themselves on a page. Tools like JAWS, NVDA, and VoiceOver allow users to pull up a list of landmarks and jump directly to any one of them. The contentinfo landmark typically contains information like copyright notices, privacy policies, and contact links that apply to the entire page. When multiple contentinfo landmarks exist, screen reader users encounter duplicate entries in their landmarks list and cannot tell which one is the actual page footer. This creates confusion and slows down navigation.
The contentinfo landmark is generated in two ways: by adding role="contentinfo" to an element, or by using a <footer> element that is a direct child of <body> (or not nested inside <article>, <aside>, <main>, <nav>, or <section>). A <footer> nested inside one of those sectioning elements does not map to the contentinfo role, so it won’t trigger this issue. However, role="contentinfo" explicitly applied to any element will always create a contentinfo landmark regardless of nesting.
Who is affected
-
Screen reader users (blind and deafblind users) rely on landmark navigation to move efficiently through a page. Duplicate
contentinfolandmarks clutter the landmarks list and make it harder to find the real page footer. - Sighted keyboard users who use browser extensions or assistive tools for landmark-based navigation are also affected.
Related standards
This rule is a Deque best practice. While not mapped to a specific WCAG success criterion, it supports the principles behind WCAG 1.3.1 Info and Relationships and WCAG 2.4.1 Bypass Blocks, which emphasize correct semantic structure and efficient navigation. The ARIA specification itself states that role="banner", role="main", and role="contentinfo" should each be used only once per page.
How to fix it
-
Audit your page for all elements that create a
contentinfolandmark. Search forrole="contentinfo"and for top-level<footer>elements. -
Keep only one
contentinfolandmark that represents the site-wide footer. -
If you need footer-like content inside articles or sections, use
<footer>nested within<article>,<section>, or<main>— these do not create acontentinfolandmark. -
Remove any extra
role="contentinfo"attributes from elements that are not the page-level footer.
Examples
Bad example: multiple contentinfo landmarks
In this example, role="contentinfo" is used on two separate elements, creating duplicate contentinfo landmarks. The <footer> at the bottom also creates a third one since it is a direct child of <body>.
<header>Visit Your Local Zoo!</header>
<main>
<h1>The Nature of the Zoo</h1>
<article>
<h2>In the Zoo: From Wild to Tamed</h2>
<p>What you see in the zoo are examples of inborn traits left undeveloped.</p>
<div role="contentinfo">
<p>Article metadata here</p>
</div>
</article>
<article>
<h2>Feeding Frenzy: The Impact of Cohabitation</h2>
<p>Some animals naturally group together with their own kind.</p>
<div role="contentinfo">
<p>Article metadata here</p>
</div>
</article>
</main>
<footer>
<p>Brought to you by North American Zoo Partnership</p>
</footer>
Good example: single contentinfo landmark
Here, role="contentinfo" is used exactly once for the page footer. Article-level footers use <footer> nested inside <article>, which does not create a contentinfo landmark.
<div role="banner">
<p>Visit Your Local Zoo!</p>
</div>
<div role="main">
<h1>The Nature of the Zoo</h1>
<article>
<h2>In the Zoo: From Wild to Tamed</h2>
<p>What you see in the zoo are examples of inborn traits left undeveloped.</p>
<footer>
<p>Article metadata here</p>
</footer>
</article>
<article>
<h2>Feeding Frenzy: The Impact of Cohabitation</h2>
<p>Some animals naturally group together with their own kind.</p>
<footer>
<p>Article metadata here</p>
</footer>
</article>
</div>
<div role="contentinfo">
<p>Brought to you by North American Zoo Partnership</p>
</div>
Good example: using semantic HTML5 elements
This version uses HTML5 semantic elements. The single top-level <footer> maps to the contentinfo role. The <footer> elements inside each <article> do not.
<header>
<p>Visit Your Local Zoo!</p>
</header>
<main>
<h1>The Nature of the Zoo</h1>
<article>
<h2>In the Zoo: From Wild to Tamed</h2>
<p>What you see in the zoo are examples of inborn traits left undeveloped.</p>
<footer>
<p>Article metadata here</p>
</footer>
</article>
<article>
<h2>Feeding Frenzy: The Impact of Cohabitation</h2>
<p>Some animals naturally group together with their own kind.</p>
<footer>
<p>Article metadata here</p>
</footer>
</article>
</main>
<footer>
<p>Brought to you by North American Zoo Partnership</p>
</footer>
What this rule checks
The axe-core rule landmark-no-duplicate-contentinfo finds all elements that map to the contentinfo landmark role, filters out any that don’t actually resolve to that role (such as <footer> elements nested inside sectioning elements), and verifies that only one contentinfo landmark remains on the page. If more than one is found, the rule reports a violation.
Landmarks are semantic regions that help assistive technology users understand the structure of a page and jump between sections efficiently. The main landmark specifically identifies the primary content area — the unique content that distinguishes this page from others on the site. When multiple main landmarks exist, screen readers present all of them in the landmarks list, making it unclear which one contains the actual primary content.
This issue primarily affects blind and deafblind users who rely on screen readers, as well as keyboard-only users with mobility disabilities who use landmark-based navigation. Screen readers like JAWS, NVDA, and VoiceOver allow users to press a shortcut key to jump directly to the main landmark. If there are two or more main landmarks, the user must guess which is correct, defeating the purpose of this navigational aid.
While this rule is categorized as a Deque best practice rather than a specific WCAG success criterion violation, it strongly supports WCAG 1.3.1 (Info and Relationships) and WCAG 2.4.1 (Bypass Blocks). Properly structured landmarks help users understand page organization and skip repetitive content.
How to Fix It
-
Audit your page for duplicate
mainlandmarks. Search your HTML for multiple<main>elements or multiple uses ofrole="main". Remember that a<main>element has an implicitrole="main", so a<main>and a<div role="main">on the same page creates a duplicate. -
Consolidate into a single
mainlandmark. Wrap all primary content in one<main>element and remove any others. -
Check iframes. If your page contains
<iframe>elements, ensure each iframe’s document also has at most onemainlandmark. -
Use complementary landmarks for other sections. Content that is not the primary focus — such as sidebars — should use
<aside>(orrole="complementary") instead of<main>.
It’s also a good practice to structure your entire page with semantic HTML5 landmark elements — <header>, <nav>, <main>, and <footer> — so all content is contained within a recognizable region. You can pair these with their ARIA equivalents (role="banner", role="navigation", role="main", role="contentinfo") for maximum compatibility across screen readers, though modern browsers and assistive technologies handle the HTML5 elements well on their own.
Examples
Incorrect: Multiple main Landmarks
This page has two main landmarks, which makes it ambiguous for screen reader users.
<header>
<h1>My Website</h1>
</header>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<main>
<h2>Articles</h2>
<p>Article content here.</p>
</main>
<main>
<h2>Sidebar</h2>
<p>Related links and info.</p>
</main>
<footer>
<p>© 2024 My Website</p>
</footer>
Incorrect: Mixing <main> with role="main"
Even though these are different elements, both create a main landmark, resulting in duplicates.
<main>
<p>Primary content here.</p>
</main>
<div role="main">
<p>More content here.</p>
</div>
Correct: Single main Landmark with Proper Page Structure
All primary content is in one <main> element, and the sidebar uses <aside> instead.
<header>
<h1>My Website</h1>
</header>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<main>
<h2>Articles</h2>
<p>Article content here.</p>
</main>
<aside>
<h2>Sidebar</h2>
<p>Related links and info.</p>
</aside>
<footer>
<p>© 2024 My Website</p>
</footer>
Correct: Page with an Iframe
The parent page and the iframe document each have at most one main landmark.
<!-- Parent page -->
<main>
<h1>Dashboard</h1>
<iframe src="widget.html" title="Statistics widget"></iframe>
</main>
<!-- widget.html -->
<main>
<p>Widget content here.</p>
</main>
Landmarks are structural regions of a page — like header, navigation, main content, and footer — that assistive technologies use to build an outline of the page. When a screen reader user opens a page, they can pull up a list of landmarks and jump directly to the section they need. Without a main landmark, there is no way for these users to skip past repeated headers and navigation to reach the content they came for. This makes the page significantly harder to use.
This rule is a Deque best practice aligned with the WAI-ARIA technique ARIA11: Using ARIA landmarks to identify regions of a page. Users who are blind, deafblind, or who have mobility impairments and rely on screen readers or alternative navigation methods are the most directly affected. Without landmarks, these users must tab or arrow through every element on the page to find the content they need.
How landmarks work
HTML5 introduced semantic elements that automatically create landmark regions:
| HTML5 Element | Implicit ARIA Role |
|---|---|
<header> |
role="banner" |
<nav> |
role="navigation" |
<main> |
role="main" |
<footer> |
role="contentinfo" |
<aside> |
role="complementary" |
Modern browsers and screen readers understand these HTML5 elements natively. For maximum compatibility, you can also add the explicit ARIA role alongside the HTML5 element (e.g., <main role="main">). This redundancy is harmless and can help with older assistive technologies.
How to fix the problem
-
Wrap your primary content in a
<main>element. Every page should have exactly one<main>landmark. -
Place all other visible content inside appropriate landmarks. Use
<header>,<nav>,<footer>, and<aside>as needed so that no content is orphaned outside a landmark region. -
Check
iframeelements. If aniframecontains landmarked content, it should have no more than one<main>landmark. -
Don’t nest
<main>elements. There should be only one<main>per page (or periframe).
Note that landmarks primarily benefit screen reader users. Sighted users and screen magnifier users don’t perceive landmarks, so they still need visible skip-navigation links to bypass repeated content.
Examples
Incorrect: no main landmark
This page has no <main> element, so screen reader users have no way to jump directly to the primary content.
<header>
<p>Company Logo</p>
</header>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<div class="content">
<p>This is the primary content of the page.</p>
</div>
<footer>
<p>© 2024 Company Name</p>
</footer>
Correct: page with one main landmark
Replacing the generic <div> with a <main> element gives screen reader users a direct navigation point to the primary content.
<header>
<p>Company Logo</p>
</header>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<main>
<p>This is the primary content of the page.</p>
</main>
<footer>
<p>© 2024 Company Name</p>
</footer>
Correct: using both HTML5 elements and ARIA roles for maximum compatibility
<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>© 2024 Company Name</p>
</footer>
Incorrect: multiple main landmarks
Having more than one <main> element confuses assistive technologies about which section is the true primary content.
<main>
<p>Article content here.</p>
</main>
<main>
<p>Sidebar content here.</p>
</main>
Correct: single main with an aside for secondary content
<main>
<p>Article content here.</p>
</main>
<aside>
<p>Sidebar content here.</p>
</aside>
HTML landmark elements like <header>, <nav>, <main>, <aside>, <form>, <section>, and <footer> — as well as elements with explicit ARIA landmark roles — create navigational waypoints that assistive technologies expose to users. Screen readers often provide a list of all landmarks on the page or allow users to jump between them using keyboard shortcuts. When two or more landmarks share the same role and have no accessible name (or share the same accessible name), they appear identical in that list. A user hearing “navigation” and “navigation” has no way of knowing whether the first is the site-wide menu and the second is a breadcrumb trail.
This issue primarily affects blind users, deafblind users, and sighted keyboard users who rely on assistive technology landmark navigation features. It is flagged as a Deque best practice rule. While it doesn’t map directly to a specific WCAG success criterion, it strongly supports the intent of WCAG 1.3.1 (Info and Relationships) and WCAG 2.4.1 (Bypass Blocks), which require that structural information is programmatically available and that users have mechanisms to navigate past repeated blocks of content.
How to Fix the Problem
The fix depends on whether you truly need multiple landmarks of the same type:
- If only one landmark of a given role exists on the page, no additional labeling is needed — it’s already unique by its role alone.
-
If multiple landmarks share the same role, give each a unique accessible name using
aria-labeloraria-labelledby. - If duplicate landmarks aren’t necessary, consider removing or consolidating them.
When choosing accessible names, pick labels that clearly describe the purpose of each landmark, such as “Primary navigation,” “Footer navigation,” or “Search form.”
Examples
Incorrect: Duplicate <nav> landmarks with no accessible names
Screen reader users see two identical “navigation” landmarks with no way to tell them apart.
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
<nav>
<a href="/terms">Terms</a>
<a href="/privacy">Privacy</a>
</nav>
Correct: Duplicate <nav> landmarks with unique accessible names
Each landmark now has a distinct label that screen readers announce.
<nav aria-label="Main">
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
<nav aria-label="Footer">
<a href="/terms">Terms</a>
<a href="/privacy">Privacy</a>
</nav>
Incorrect: Two <aside> elements with the same aria-label
Even though labels are present, they are identical, so the landmarks are still indistinguishable.
<aside aria-label="Related content">
<p>Popular articles</p>
</aside>
<aside aria-label="Related content">
<p>Recommended products</p>
</aside>
Correct: Two <aside> elements with unique aria-label values
<aside aria-label="Popular articles">
<p>Popular articles</p>
</aside>
<aside aria-label="Recommended products">
<p>Recommended products</p>
</aside>
Correct: Using aria-labelledby to reference visible headings
If your landmarks already contain headings, you can point to those headings instead of duplicating text in aria-label.
<nav aria-labelledby="nav-main-heading">
<h2 id="nav-main-heading">Main Menu</h2>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<nav aria-labelledby="nav-breadcrumb-heading">
<h2 id="nav-breadcrumb-heading">Breadcrumb</h2>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
Correct: Only one landmark of a given role — no label needed
When a landmark role appears only once on the page, it is inherently unique.
<header>
<h1>My Website</h1>
</header>
<main>
<p>Page content goes here.</p>
</main>
<footer>
<p>© 2024 My Website</p>
</footer>
Incorrect: Duplicate ARIA landmark roles without unique names
Using explicit ARIA roles doesn’t change the requirement — duplicates still need unique names.
<div role="complementary">
<p>Sidebar content A</p>
</div>
<div role="complementary">
<p>Sidebar content B</p>
</div>
Correct: Explicit ARIA landmark roles with unique accessible names
<div role="complementary" aria-label="Author bio">
<p>Sidebar content A</p>
</div>
<div role="complementary" aria-label="Related links">
<p>Sidebar content B</p>
</div>
The <meta name="viewport"> element controls how a page is displayed on mobile and responsive layouts. Two of its parameters — user-scalable and maximum-scale — directly affect whether users can zoom in on page content. Setting user-scalable="no" completely disables pinch-to-zoom and browser zoom controls, while a low maximum-scale value (e.g., 1 or 2) caps how far a user can zoom in.
Why This Matters
People with low vision frequently rely on zoom and screen magnification to read web content. When zooming is disabled or restricted, these users may be unable to perceive text, images, or interactive elements at all. This creates a significant barrier to accessing information.
WCAG Success Criterion 1.4.4 (Resize Text) requires that text can be resized up to 200% without loss of content or functionality. However, as a best practice recommended by Deque, pages should support zooming up to 500% (5x). This higher threshold better accommodates users who need substantial magnification and aligns with the principle of providing the widest possible range of accessibility.
This rule is classified as a Deque Best Practice and primarily affects users with low vision. While it goes beyond the strict WCAG 200% requirement, supporting 5x zoom is a meaningful improvement that costs nothing to implement.
How to Fix It
-
Remove
user-scalable="no"from thecontentattribute of the<meta name="viewport">element. If present, either remove it entirely or set it toyes. -
Ensure
maximum-scaleis at least5(representing 500% zoom). If you don’t need to cap zoom at all, simply omit themaximum-scaleparameter — browsers will allow unrestricted zooming by default.
Examples
Incorrect: Zooming Disabled
This viewport meta tag prevents users from zooming at all:
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
Incorrect: Zoom Restricted Below 500%
This viewport meta tag limits zooming to 200%, which falls short of the recommended 500%:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
Correct: Zooming Allowed Up to 500%
This viewport meta tag explicitly permits zooming and sets the maximum scale to 5:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5">
Correct: No Zoom Restrictions
The simplest and most accessible approach is to omit both user-scalable and maximum-scale entirely, allowing the browser to handle zoom without limits:
<meta name="viewport" content="width=device-width, initial-scale=1">
Correct: Explicitly Enabling User Scaling
You can also explicitly set user-scalable=yes for clarity, though this is the default behavior:
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes, maximum-scale=5">
Key Points
- The browser viewport (the visible area of the page) is separate from programmatic focus. When a user zooms in, only a portion of the page is visible at a time, but focus does not automatically follow the viewport. This is expected behavior — users scroll and pan to find content.
- Removing zoom restrictions does not break layouts when responsive design practices are followed. Modern CSS and flexible layouts adapt naturally to zoom.
-
This rule checks that
user-scalable="no"is not present in the<meta name="viewport">element, and thatmaximum-scaleis not less than 5 (500%).
Screen reader users rely heavily on heading navigation to move efficiently through web pages. Most screen readers provide keyboard shortcuts (such as pressing H to jump to the next heading, or 1 to jump to the next h1) that let users skip directly to the main content. When a page lacks a level-one heading, these shortcuts fail, and users are forced to listen through navigation menus, banners, and other content before reaching what they came for.
This is fundamentally a problem of perceivability and navigability. Sighted users can glance at a page and instantly understand its layout — they see the large, bold title and know where the main content begins. Blind, low-vision, and deafblind users don’t have that option. For them, headings serve as a structural outline of the page. A well-organized heading hierarchy starting with an h1 gives these users an equivalent way to quickly grasp the page’s structure and jump to the content they need.
This rule is a Deque best practice and aligns with broader accessibility principles in WCAG, including Success Criterion 1.3.1 (Info and Relationships), which requires that structural information conveyed visually is also available programmatically, and Success Criterion 2.4.6 (Headings and Labels), which requires headings to be descriptive. While not a strict WCAG failure, the absence of an h1 has a moderate impact on usability for assistive technology users.
How to Fix It
-
Add a single
h1element at the beginning of your page’s main content. This heading should describe the primary topic or purpose of the page. -
Use only one
h1per page. While HTML5 technically allows multipleh1elements, best practice is to use a singleh1that represents the top-level topic. -
Build a logical heading hierarchy. After the
h1, useh2for major sections,h3for subsections within those, and so on. Don’t skip heading levels (e.g., jumping fromh1toh4). -
Handle iframes carefully. If your page includes an
iframe, the heading hierarchy inside that iframe should fit within the parent page’s heading structure. If the parent page already has anh1, the iframe content should start withh2or lower, depending on where it sits in the hierarchy. When the iframe contains third-party content you can’t control, this may not always be possible, but it’s still the ideal approach.
What the Rule Checks
This rule verifies that the page (or at least one of its frames) contains at least one element matching either h1:not([role]) or [role="heading"][aria-level="1"].
Examples
Incorrect: Page with No Level-One Heading
This page jumps straight to h2 headings without ever establishing an h1. Screen reader users have no top-level heading to navigate to.
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<main>
<h2>Welcome to Our Store</h2>
<p>Browse our latest products below.</p>
<h2>Featured Products</h2>
<p>Check out what's new this week.</p>
</main>
</body>
Correct: Page with a Level-One Heading
The h1 clearly identifies the page’s main topic and appears at the start of the main content. Subsections use h2.
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<main>
<h1>Welcome to Our Store</h1>
<p>Browse our latest products below.</p>
<h2>Featured Products</h2>
<p>Check out what's new this week.</p>
</main>
</body>
Correct: Using ARIA to Create a Level-One Heading
If you can’t use a native h1 element, you can use the role="heading" and aria-level="1" attributes on another element. Native HTML headings are always preferred, but this approach is valid.
<main>
<div role="heading" aria-level="1">Welcome to Our Store</div>
<p>Browse our latest products below.</p>
</main>
Correct: Page with an Iframe That Fits the Heading Hierarchy
When embedding content in an iframe, the iframe’s heading structure should continue from the parent page’s hierarchy rather than introducing a second h1.
<!-- Parent page -->
<body>
<main>
<h1>Company Dashboard</h1>
<h2>Recent Activity</h2>
<iframe src="activity-feed.html" title="Activity feed"></iframe>
</main>
</body>
<!-- activity-feed.html -->
<body>
<h3>Today's Updates</h3>
<p>Three new items were added.</p>
</body>
The role="none" and role="presentation" attributes tell browsers to strip the semantic meaning from an element, effectively removing it from the accessibility tree. This is useful when elements are used purely for visual layout and carry no meaningful content for assistive technology users.
However, the WAI-ARIA specification defines specific conditions under which this removal is overridden. If a presentational element has a global ARIA attribute (such as aria-hidden, aria-label, aria-live, aria-describedby, etc.) or is focusable (either natively, like a <button>, or through tabindex), the browser must ignore the presentational role and keep the element in the accessibility tree. This is known as a presentational role conflict.
When this conflict occurs, screen reader users may encounter elements that were intended to be hidden but are instead announced — potentially with confusing or missing context. This creates a disorienting experience, particularly for blind users and users with low vision who rely on screen readers to understand the page structure.
This rule is flagged as a Deque best practice. While not mapped to a specific WCAG success criterion, it supports the broader goal of ensuring the accessibility tree accurately represents the author’s intent, which contributes to a coherent experience under WCAG principles like 1.3.1 Info and Relationships and 4.1.2 Name, Role, Value.
How to Fix It
For every element with role="none" or role="presentation", ensure that:
-
No global ARIA attributes are present. Remove attributes like
aria-hidden,aria-label,aria-live,aria-describedby,aria-atomic, and any other global ARIA attributes. -
The element is not focusable. Don’t use natively focusable elements (like
<button>,<a href>, or<input>) with a presentational role. Also, don’t addtabindexto a presentational element.
If the element genuinely needs a global ARIA attribute or needs to be focusable, then it shouldn’t have a presentational role — remove role="none" or role="presentation" instead.
Examples
Incorrect: Presentational element with a global ARIA attribute
The aria-hidden="true" attribute is a global ARIA attribute, which creates a conflict with role="none":
<li role="none" aria-hidden="true">Decorative item</li>
Incorrect: Natively focusable element with a presentational role
A <button> is natively focusable, so its presentational role will be ignored by the browser:
<button role="none">Click me</button>
Incorrect: Presentational element made focusable via tabindex
Adding tabindex="0" makes the element focusable, overriding the presentational role:
<img alt="" role="presentation" tabindex="0">
Correct: Presentational element with no conflicts
These elements have no global ARIA attributes and are not focusable, so they will be properly removed from the accessibility tree:
<li role="none">Layout item</li>
<li role="presentation">Layout item</li>
<img alt="" role="presentation">
Correct: Removing the presentational role when focus is needed
If the element needs to be focusable, remove the presentational role and give it an appropriate accessible name instead:
<button aria-label="Submit form">Submit</button>
Ready to validate your sites?
Start your free trial today.