Accessibility Guides for deafblind
Learn how to identify and fix common accessibility issues flagged by Axe Core — so your pages are inclusive and usable for everyone. Also check our HTML Validation Guides.
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
contentinfo landmarks 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
contentinfo landmark. Search for role="contentinfo" and for top-level <footer> elements. - Keep only one
contentinfo landmark 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 a contentinfo landmark. - 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
main landmarks. Search your HTML for multiple <main> elements or multiple uses of role="main". Remember that a <main> element has an implicit role="main", so a <main> and a <div role="main"> on the same page creates a duplicate. - Consolidate into a single
main landmark. 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 one main landmark. - Use complementary landmarks for other sections. Content that is not the primary focus — such as sidebars — should use
<aside> (or role="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
iframe elements. If an iframe contains 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 per iframe).
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-label or aria-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>
Links are one of the most fundamental interactive elements on the web. When a link lacks an accessible name, screen reader users hear something like "link" with no further context, making it impossible to understand the link's purpose or destination. This affects people who are blind, deafblind, or have low vision and rely on screen readers, as well as keyboard-only users who navigate through links sequentially.
A link's accessible name can come from several sources: its visible text content, an aria-label attribute, an aria-labelledby reference, a title attribute, or the alt text of an image contained within it. If none of these provide a non-empty string, the link has no discernible text, and this rule will flag it.
Beyond just having text, links must also be programmatically focusable. Avoid relying on device-specific JavaScript events like onmouseover or mouseout(), which are inaccessible to keyboard users. Use device-independent alternatives like onfocus, onblur, focus(), and blur(). Additionally, don't hide link text from assistive technologies using display: none, visibility: hidden, or aria-hidden="true" on the link or its text content.
It's also important to use semantic <a> elements with a valid href attribute rather than simulating links with <span> or <div> elements and click handlers. Real links are focusable by default and convey the correct role to assistive technologies.
Related WCAG Success Criteria
This rule relates to two Level A success criteria that apply across WCAG 2.0, 2.1, and 2.2:
- WCAG 2.4.4 (Link Purpose in Context): Users must be able to determine the purpose of each link from the link text alone, or from the link text combined with its programmatically determined context.
- WCAG 4.1.2 (Name, Role, Value): All user interface components, including links, must have an accessible name and role that can be programmatically determined.
This rule is also required under Section 508, EN 301 549, and Trusted Tester guidelines.
How to Fix It
- Add visible text content inside the
<a> element. - For image links, provide meaningful
alt text on the <img> element. - Use
aria-label when you need to provide an accessible name that differs from or supplements the visible text. - Use
aria-labelledby to reference another element's text as the link's accessible name. - Don't hide link text from screen readers with
aria-hidden="true" or CSS that removes elements from the accessibility tree. - Ensure links are focusable by using proper
<a> elements with href attributes and avoiding mouse-only event handlers.
Examples
Empty link (incorrect)
A link with no text or accessible name:
<a href="/settings"></a>
A link that only contains an image with no alt text:
<a href="/home">
<img src="logo.png" alt="">
</a>
A link with its text hidden from assistive technologies:
<a href="/profile">
<span aria-hidden="true">Profile</span>
</a>
Link with visible text (correct)
<a href="/settings">Settings</a>
Image link with descriptive alt text (correct)
<a href="/home">
<img src="logo.png" alt="Homepage">
</a>
Link with aria-label (correct)
This is useful when multiple links share the same visible text, such as repeated "Read more" links:
<h3>Accessibility Updates</h3>
<p>New WCAG 2.2 guidelines have been published.
<a href="/wcag22" aria-label="Read more about WCAG 2.2 updates">Read more</a>
</p>
<h3>Screen Reader Tips</h3>
<p>Learn how to navigate tables with a screen reader.
<a href="/sr-tips" aria-label="Read more about screen reader tips">Read more</a>
</p>
Link with aria-labelledby (correct)
<h3 id="report-title">Annual Report 2024</h3>
<a href="/report-2024.pdf" aria-labelledby="report-title">Download PDF</a>
Icon link with visually hidden text (correct)
For links that use icons without visible text, use a visually hidden <span> to provide an accessible name:
<a href="/search">
<svg aria-hidden="true" focusable="false">
<use href="#icon-search"></use>
</svg>
<span class="visually-hidden">Search</span>
</a>
The visually-hidden class hides the text visually but keeps it available to screen readers:
.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;
}
According to the HTML specification, the only permitted content directly inside <ul> and <ol> elements are <li> elements, plus non-content elements like <script> and <template>. When you place other content elements — such as <div>, <span>, <p>, <a>, or plain text nodes — directly inside a list container, you create an invalid structure that breaks the semantic relationship between the list and its items.
Why This Matters
Screen readers announce lists in a specific way to help users navigate and comprehend grouped content. When a user encounters a properly structured list, their screen reader will typically announce something like "list, 5 items" and then allow the user to move through each item individually. This behavior depends entirely on the list being structured correctly.
When non-<li> content elements appear as direct children of <ul> or <ol>, screen readers may:
- Fail to announce the total number of items in the list
- Skip over improperly nested content entirely
- Present list items and non-list content in a confusing, disjointed manner
This primarily affects blind and deafblind users who rely on screen readers, but it also impacts anyone using assistive technology that parses the DOM structure to present content.
Related WCAG Success Criteria
This rule maps to WCAG 2.0, 2.1, and 2.2 Success Criterion 1.3.1: Info and Relationships (Level A). This criterion requires that information, structure, and relationships conveyed through visual presentation can be programmatically determined. When a list is visually presented as a group of related items but its underlying HTML structure is invalid, the relationship between the list container and its items cannot be reliably communicated to assistive technology.
How to Fix It
- Ensure every content element inside a
<ul> or <ol> is wrapped in an <li>. If you have <div>, <span>, <p>, <a>, or any other content element as a direct child of the list, move it inside an <li>. - Move non-list content outside the list. If you have headings, paragraphs, or other content that isn't a list item, place it before or after the list element rather than inside it.
- Nest sub-lists inside
<li> elements. If you need a nested <ul> or <ol>, it must be placed inside an <li> of the parent list, not directly as a child of the parent list.
Examples
Incorrect: <div> elements directly inside a <ul>
<ul>
<div>Apples</div>
<div>Bananas</div>
<div>Cherries</div>
</ul>
Screen readers cannot identify these <div> elements as list items.
Correct: <li> elements as direct children
<ul>
<li>Apples</li>
<li>Bananas</li>
<li>Cherries</li>
</ul>
Incorrect: Heading placed directly inside a list
<ul>
<h3>Fruits</h3>
<li>Apples</li>
<li>Bananas</li>
<li>Cherries</li>
</ul>
Correct: Heading moved outside the list
<h3>Fruits</h3>
<ul>
<li>Apples</li>
<li>Bananas</li>
<li>Cherries</li>
</ul>
Incorrect: Nested list placed directly inside a <ul>
<ul>
<li>Fruits</li>
<ul>
<li>Apples</li>
<li>Bananas</li>
</ul>
<li>Vegetables</li>
</ul>
Correct: Nested list placed inside an <li>
<ul>
<li>
Fruits
<ul>
<li>Apples</li>
<li>Bananas</li>
</ul>
</li>
<li>Vegetables</li>
</ul>
Incorrect: Link directly inside an <ol>
<ol>
<a href="/step-1"><li>Step one</li></a>
<a href="/step-2"><li>Step two</li></a>
</ol>
Correct: Link placed inside the <li>
<ol>
<li><a href="/step-1">Step one</a></li>
<li><a href="/step-2">Step two</a></li>
</ol>
When a page refreshes automatically, the browser reloads the entire document and moves focus back to the top of the page. This means a user who was partway through reading content or filling out a form suddenly loses their place with no warning. For screen reader users, this is particularly disruptive — they must navigate from the beginning of the page again. Users with cognitive disabilities or those who read more slowly may not have finished processing the content before it disappears. People with mobility impairments who navigate slowly with alternative input devices are also affected, as their progress through the page is reset.
Delayed refreshes also constitute an interruption that the user cannot suppress, postpone, or control. Even if the delay is long (e.g., 60 seconds), the user has no way to opt out or extend the timer.
Related WCAG Success Criteria
This rule relates to two AAA-level WCAG success criteria:
- WCAG 2.2.4 (Interruptions): Interruptions must be able to be postponed or suppressed by the user, except in emergencies. An automatic page refresh is an interruption that the user cannot control.
- WCAG 3.2.5 (Change on Request): Changes of context must be initiated only by user request, or a mechanism must be available to turn off such changes. An automatic refresh or redirect is a change of context that occurs without user action.
How to Fix It
- Remove the
http-equiv="refresh" attribute from every meta element that contains it. - For redirects, use server-side HTTP redirects (e.g., a
301 or 302 status code) instead of client-side meta refresh. This is more reliable and does not cause accessibility issues. - For periodic content updates, use JavaScript to fetch and update only the changed content without reloading the entire page. Provide users with controls to pause, extend, or disable the automatic updates.
Examples
Incorrect: Using Meta Refresh to Redirect After a Delay
This refreshes the page after 40 seconds, redirecting the user to a new URL without their consent:
<head>
<meta http-equiv="refresh" content="40; url=https://example.com/new-page">
</head>
Incorrect: Using Meta Refresh to Reload the Page
This reloads the current page every 60 seconds:
<head>
<meta http-equiv="refresh" content="60">
</head>
Correct: Server-Side Redirect
Instead of using a meta refresh for redirection, configure your server to return an HTTP redirect. For example, in an .htaccess file:
Redirect 301 /old-page https://example.com/new-page
The HTML page itself contains no meta refresh:
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
Correct: JavaScript with User Control for Content Updates
If you need to periodically update content, use JavaScript and give the user the ability to control the behavior:
<head>
<meta charset="utf-8">
<title>Live Dashboard</title>
</head>
<body>
<h1>Live Dashboard</h1>
<button id="toggle-refresh">Pause Auto-Refresh</button>
<div id="content">
<p>Dashboard content goes here.</p>
</div>
<script>
let refreshInterval = setInterval(updateContent, 60000);
let isActive = true;
document.getElementById("toggle-refresh").addEventListener("click", function () {
if (isActive) {
clearInterval(refreshInterval);
this.textContent = "Resume Auto-Refresh";
} else {
refreshInterval = setInterval(updateContent, 60000);
this.textContent = "Pause Auto-Refresh";
}
isActive = !isActive;
});
function updateContent() {
// Fetch and update only the content area
}
</script>
</body>
This approach keeps the user in control. They can pause updates when they need more time to read content, and resume when ready — satisfying both WCAG 2.2.4 and 3.2.5.
What This Rule Checks
The axe rule meta-refresh-no-exceptions checks for the presence of the http-equiv="refresh" attribute on any meta element in the document. If found, the rule flags it as a violation regardless of the delay value, since even long delays deny the user control over when the refresh occurs.
When a page includes a <meta http-equiv="refresh"> tag with a time value under 20 hours, the browser will automatically reload or redirect the page after that delay. This happens without any user interaction or consent, which creates significant barriers for people with disabilities.
Why This Is a Problem
Automatic page refreshes are disorienting for many users. When the page reloads, the browser moves focus back to the top of the document. A user who was partway through reading content or filling out a form loses their place entirely. This is especially harmful for:
- Screen reader users (blind and deafblind users): A screen reader announces content linearly from the top. An unexpected refresh forces the user to start over, losing context and progress.
- Users with mobility impairments: People who navigate slowly using switch devices, mouth sticks, or other assistive technology may not finish interacting with the page before it refreshes, causing them to lose their work.
- Users with cognitive disabilities: Unexpected changes to the page can be confusing and make it difficult to complete tasks.
This rule relates to WCAG 2.2 Success Criterion 2.2.1: Timing Adjustable (Level A), which requires that for each time limit set by the content, users can turn off, adjust, or extend that time limit. A <meta> refresh gives users no such control — the page simply reloads or redirects with no warning or option to prevent it.
How to Fix It
You have several options depending on your use case:
- Remove the
meta refresh entirely. If the refresh is unnecessary, simply delete the tag. - Set the delay to 20 hours or more. If a refresh is truly needed, a delay of 20 hours or greater passes the rule, as it's unlikely to interrupt a user's session.
- Use a server-side redirect. If the goal is to redirect users to a new URL, configure a
301 or 302 redirect on the server instead. - Use JavaScript with user controls. If you need periodic content updates, use JavaScript and provide users with the ability to pause, extend, or disable the auto-refresh.
Examples
Failing: Auto-refresh after 30 seconds
This refreshes the page every 30 seconds with no way for the user to stop it.
<head>
<meta http-equiv="refresh" content="30">
</head>
Failing: Delayed redirect after 10 seconds
This redirects the user to another page after 10 seconds, giving them no control over the timing.
<head>
<meta http-equiv="refresh" content="10; url=https://example.com/new-page">
</head>
Passing: Meta refresh removed
The simplest fix is to remove the meta refresh tag entirely.
<head>
<title>My Page</title>
</head>
Passing: Server-side redirect instead of client-side
For redirects, use a server-side response. For example, an HTTP 301 header:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/new-page
Passing: JavaScript refresh with user control
If you need periodic updates, use JavaScript and give users a way to stop or adjust the refresh.
<head>
<title>Live Dashboard</title>
</head>
<body>
<button id="toggle-refresh">Pause Auto-Refresh</button>
<script>
let refreshInterval = setInterval(function () {
location.reload();
}, 300000); // 5 minutes
document.getElementById("toggle-refresh").addEventListener("click", function () {
if (refreshInterval) {
clearInterval(refreshInterval);
refreshInterval = null;
this.textContent = "Resume Auto-Refresh";
} else {
refreshInterval = setInterval(function () {
location.reload();
}, 300000);
this.textContent = "Pause Auto-Refresh";
}
});
</script>
</body>
Passing: Delay set to 20 hours or more
If a refresh is absolutely necessary and no interactive alternative is feasible, setting the delay to at least 72,000 seconds (20 hours) will pass the rule.
<head>
<meta http-equiv="refresh" content="72000">
</head>
When a <video> or <audio> element plays sound automatically, it competes with assistive technology output. Screen reader users rely on hearing their screen reader's spoken output to navigate and interact with web content. If background audio starts playing as soon as they land on a page, it can drown out the screen reader, making it difficult or impossible to find and operate the very control needed to stop the unwanted sound. This creates a frustrating catch-22: the user needs to hear their screen reader to find the stop button, but the auto-playing audio prevents them from hearing the screen reader.
People with cognitive disabilities can also be significantly affected, as unexpected audio can be disorienting, distracting, and make it harder to process page content.
Related WCAG Success Criteria
This rule maps to WCAG Success Criterion 1.4.2: Audio Control (Level A), which requires that if any audio on a web page plays automatically for more than three seconds, either a mechanism is available to pause or stop the audio, or a mechanism is available to control audio volume independently from the overall system volume level. This is a Level A requirement, meaning it is considered a minimum baseline for accessibility across WCAG 2.0, 2.1, and 2.2.
How to Fix the Problem
Use one of the following approaches:
Don't autoplay audio (strongly preferred). Let the user initiate playback themselves. This is the best approach because it gives users full control and avoids any interference with assistive technology.
Keep auto-playing audio under three seconds. If the audio stops within three seconds and does not loop, it is unlikely to significantly disrupt screen reader usage.
Provide an accessible control mechanism. If audio must autoplay for more than three seconds, include a clearly visible, keyboard-accessible control near the top of the page that allows users to pause, stop, or mute the audio. The control must be reachable before the user needs to navigate through a lot of content.
Important Considerations
- The
controls attribute on <audio> and <video> elements satisfies the requirement for a control mechanism, as it provides built-in browser controls for play, pause, and volume. - A short audio clip that uses the
loop attribute will effectively play indefinitely, so it fails this rule even if the source file is under three seconds. - Custom controls must be keyboard accessible and properly labeled for screen readers.
Examples
Failing: Autoplay with no controls
This <audio> element autoplays and provides no way for the user to stop or mute it:
<audio autoplay src="background-music.mp3">
</audio>
Failing: Short audio that loops indefinitely
Even though the audio file is under three seconds, the loop attribute causes it to repeat forever:
<audio autoplay loop src="chime.mp3">
</audio>
Failing: Video with autoplay and no controls
<video autoplay src="promo.mp4">
</video>
Passing: Autoplay with built-in controls
Adding the controls attribute gives users the ability to pause, stop, and adjust volume:
<audio autoplay controls src="background-music.mp3">
</audio>
<video autoplay controls src="promo.mp4">
</video>
Passing: No autoplay — user initiates playback
The best approach is to let the user decide when to start playback:
<audio controls src="background-music.mp3">
</audio>
Passing: Autoplay with short duration and no loop
If the audio is under three seconds and does not loop, it is acceptable:
<video autoplay src="short-intro.mp4">
</video>
Passing: Autoplay with a custom accessible mute button
If you cannot use the native controls attribute, provide a custom control that is keyboard accessible and labeled:
<audio id="bg-audio" autoplay src="background-music.mp3">
</audio>
<button type="button" onclick="document.getElementById('bg-audio').pause()">
Pause background audio
</button>
How the Rule Works
The axe-core rule no-autoplay-audio evaluates <audio> and <video> elements and checks whether they autoplay audio for more than three seconds without a control mechanism. Specifically:
- If the element has no source, the result is incomplete (duration cannot be determined).
- If the element can autoplay and has no controls mechanism, it fails.
- If the element plays under three seconds but loops, it fails (because the effective duration is infinite).
- If the element can autoplay but has a controls mechanism, it passes.
- If the element can autoplay and the duration is under three seconds (without looping), it passes.
The <object> element embeds external content into a web page — this could be a video, an audio player, an interactive applet, a PDF, or even another HTML page. Unlike images, which have a straightforward alt attribute, <object> elements rely on other mechanisms to provide text alternatives. When no alternative text is present, screen readers encounter the embedded object but have nothing meaningful to announce, leaving blind and deafblind users unable to understand what the content is or what purpose it serves.
Why this matters
Screen readers cannot interpret non-text content on their own. They depend entirely on text alternatives provided in the markup. When an <object> element lacks alternative text, users of assistive technology are left with no information about the embedded content. This is a serious barrier that can cause users to miss critical functionality or information on a page.
This rule relates to WCAG 2.0/2.1/2.2 Success Criterion 1.1.1: Non-text Content (Level A), which requires that all non-text content presented to the user has a text alternative that serves an equivalent purpose. It is also required by Section 508 and EN 301 549.
How to fix it
Add alternative text to every <object> element using one of these approaches:
title attribute — Add a title attribute directly on the <object> element with a concise description.aria-label attribute — Provide an accessible name using aria-label.aria-labelledby attribute — Reference the id of another element that contains the descriptive text.role="presentation" or role="none" — If the embedded object is purely decorative and conveys no information, you can remove it from the accessibility tree entirely.
Writing effective alternative text
When crafting alternative text, consider these questions:
- Why is this embedded content on the page?
- What information does it present to sighted users?
- What purpose or function does it serve?
- If you removed the object, what words would you use to convey the same information?
Keep the text concise and meaningful. Avoid generic terms like "object," "video," or file names — instead, describe the content's actual purpose. For example, "Quarterly revenue chart for 2024" is far more useful than "chart.swf."
Examples
Incorrect: no alternative text
The <object> element has no accessible name, so screen readers cannot describe it.
<object data="quarterly-report.pdf"></object>
Incorrect: inner content used as fallback text
Text placed inside the <object> element is intended as fallback content for browsers that cannot render the object — it is not reliably exposed as an accessible name by screen readers.
<object data="quarterly-report.pdf">
This object shows the quarterly report.
</object>
Incorrect: only whitespace inside the element
<object data="quarterly-report.pdf">
<div> </div>
</object>
Correct: using title
<object data="quarterly-report.pdf" title="2024 Q3 quarterly revenue report"></object>
Correct: using aria-label
<object data="quarterly-report.pdf" aria-label="2024 Q3 quarterly revenue report"></object>
Correct: using aria-labelledby
<h2 id="report-heading">2024 Q3 Quarterly Revenue Report</h2>
<object data="quarterly-report.pdf" aria-labelledby="report-heading"></object>
Correct: decorative object with role="none"
If the embedded object is purely decorative and adds no informational value, remove it from the accessibility tree:
<object data="decorative-animation.swf" role="none"></object>
When a developer uses CSS to make a <p> element look like a heading — by increasing font size, adding bold weight, or applying other visual styling — sighted users may perceive it as a heading, but assistive technologies cannot. Screen readers identify headings by their semantic markup, not their visual appearance. A <p> element styled to look like a heading is still announced as a plain paragraph, which means the document's structure is invisible to anyone who depends on programmatic heading navigation.
This issue primarily affects blind and deafblind users who rely on screen readers, as well as users with mobility impairments who navigate via keyboard shortcuts. Screen reader users frequently jump between headings to skim a page's content — similar to how sighted users visually scan for larger, bolder text. When headings are not properly marked up, these users must listen through all content linearly, wasting significant time and effort.
This rule relates to WCAG 2.1 Success Criterion 1.3.1: Info and Relationships, which requires that information, structure, and relationships conveyed through presentation are programmatically determinable or available in text. When a paragraph is styled to look like a heading, the structural relationship it implies (a section label) is only conveyed visually and fails this criterion.
How to fix it
- Identify styled paragraphs acting as headings. Look for
<p> elements with CSS that increases font-size, applies font-weight: bold, or otherwise makes them visually distinct in a way that suggests a heading. - Replace them with semantic heading elements. Use
<h1> through <h6> based on the element's position in the document hierarchy. - Maintain a logical heading order. Start the main content with an
<h1>, use <h2> for major sections, <h3> for sub-sections within those, and so on. Avoid skipping levels (e.g., jumping from <h2> to <h4>). - Move visual styling to the heading element. Apply any desired CSS styles to the heading element instead of the paragraph.
As a best practice, the <h1> should appear at the beginning of the main content so screen reader users can jump directly to it using keyboard shortcuts. Sub-sections should use <h2>, with deeper nesting using <h3> through <h6> as needed. Headings should be brief, clear, and unique to maximize their usefulness as navigation landmarks.
Beyond accessibility, proper heading structure also benefits SEO, since search engines use headings to understand and rank page content.
Examples
Incorrect: styled paragraph used as a heading
In this example, a <p> element is visually styled to look like a heading but provides no semantic heading information to assistive technologies.
<style>
.fake-heading {
font-size: 24px;
font-weight: bold;
margin-top: 1em;
}
</style>
<p class="fake-heading">Our Services</p>
<p>We offer a wide range of consulting services.</p>
Correct: proper heading element with styling
Replace the styled <p> with an appropriate heading element. The same visual styles can be applied to the heading if needed.
<style>
h2 {
font-size: 24px;
font-weight: bold;
margin-top: 1em;
}
</style>
<h2>Our Services</h2>
<p>We offer a wide range of consulting services.</p>
Incorrect: multiple styled paragraphs mimicking a heading hierarchy
<p style="font-size: 32px; font-weight: bold;">Welcome to Our Site</p>
<p>Some introductory content here.</p>
<p style="font-size: 24px; font-weight: bold;">About Us</p>
<p>Learn more about our team.</p>
<p style="font-size: 18px; font-weight: bold;">Our Mission</p>
<p>We strive to make the web accessible.</p>
Correct: semantic heading hierarchy
<h1>Welcome to Our Site</h1>
<p>Some introductory content here.</p>
<h2>About Us</h2>
<p>Learn more about our team.</p>
<h3>Our Mission</h3>
<p>We strive to make the web accessible.</p>
What this rule checks
This rule examines <p> elements and flags any that have been styled to visually resemble headings through CSS properties such as increased font-size, font-weight: bold, or font-style: italic. If a paragraph's styling makes it look like a heading, it should be converted to a proper heading element instead.
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
h1 element at the beginning of your page's main content. This heading should describe the primary topic or purpose of the page. - Use only one
h1 per page. While HTML5 technically allows multiple h1 elements, best practice is to use a single h1 that represents the top-level topic. - Build a logical heading hierarchy. After the
h1, use h2 for major sections, h3 for subsections within those, and so on. Don't skip heading levels (e.g., jumping from h1 to h4). - 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 an h1, the iframe content should start with h2 or 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>
Landmark regions give a web page its structural backbone. They allow screen reader users to jump directly to major sections — like the navigation, main content, or footer — without having to tab or arrow through every element on the page. This is similar to how sighted users visually scan a page layout to find what they need.
When content exists outside of any landmark, screen readers may skip over it entirely during landmark navigation, or users may encounter it without any context about what section of the page it belongs to. This primarily affects users who are blind or deafblind, as well as users with mobility impairments who rely on assistive technologies to navigate efficiently.
This rule is flagged as a Deque best practice and is also referenced in the RGAA (the French government accessibility standard). While it doesn't map directly to a specific WCAG success criterion, it strongly supports WCAG 1.3.1 (Info and Relationships) and WCAG 2.4.1 (Bypass Blocks) by ensuring that page structure is programmatically conveyed to assistive technologies.
How Landmarks Work
HTML5 introduced semantic elements that automatically create landmark regions:
HTML5 Element Equivalent ARIA Role Purpose <header>bannerSite-wide header (when not nested inside <article> or <section>) <nav>navigationNavigation links <main>mainPrimary page content <footer>contentinfoSite-wide footer (when not nested inside <article> or <section>) <aside>complementarySupporting content <section> (with accessible name)regionA distinct section of the page <form> (with accessible name)formA form region
Using native HTML5 elements is preferred over ARIA roles. The general principle is: if a native HTML element provides the landmark semantics you need, use it instead of adding role attributes to generic <div> elements.
How to Fix the Problem
- Audit your page for any content that sits directly inside
<body> without being wrapped in a landmark element. - Wrap orphaned content in the appropriate landmark. Most body content belongs inside
<main>. Headers and footers belong in <header> and <footer>. Navigation belongs in <nav>. - Skip links are the exception — a skip navigation link placed before the first landmark is acceptable and does not need to be wrapped in a landmark.
- Use
<main> as a catch-all for any content that doesn't belong in the header, footer, or navigation. Every page should have exactly one <main> element.
Examples
Incorrect: Content Outside Landmarks
In this example, the introductory paragraph and the promotional banner sit outside any landmark region. A screen reader user navigating by landmarks would miss them.
<body>
<header>
<h1>My Website</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
<p>Welcome to our site! Check out our latest offerings.</p>
<main>
<h2>Featured Products</h2>
<p>Browse our catalog below.</p>
</main>
<div class="promo-banner">
<p>Free shipping on orders over $50!</p>
</div>
<footer>
<p>© 2024 My Website</p>
</footer>
</body>
The <p> element after <header> and the <div class="promo-banner"> are not inside any landmark.
Correct: All Content Inside Landmarks
Move the orphaned content into appropriate landmarks:
<body>
<header>
<h1>My Website</h1>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
<main>
<p>Welcome to our site! Check out our latest offerings.</p>
<h2>Featured Products</h2>
<p>Browse our catalog below.</p>
<aside>
<p>Free shipping on orders over $50!</p>
</aside>
</main>
<footer>
<p>© 2024 My Website</p>
</footer>
</body>
Correct: Basic Page Structure with HTML5 Landmarks
<body>
<header>
<h1>Site Name</h1>
</header>
<nav>
<a href="/">Home</a>
<a href="/contact">Contact</a>
</nav>
<main>
<h2>Page Title</h2>
<p>All primary content goes here.</p>
</main>
<footer>
<p>© 2024 Site Name</p>
</footer>
</body>
Correct: Using ARIA Roles (When HTML5 Elements Are Not an Option)
If you cannot use HTML5 semantic elements, apply ARIA landmark roles to generic elements:
<body>
<div role="banner">
<h1>Site Name</h1>
</div>
<div role="navigation">
<a href="/">Home</a>
<a href="/contact">Contact</a>
</div>
<div role="main">
<h2>Page Title</h2>
<p>All primary content goes here.</p>
</div>
<div role="contentinfo">
<p>© 2024 Site Name</p>
</div>
</body>
This approach is valid but less preferred. Native HTML5 elements are clearer, require less code, and are better supported across browsers and assistive technologies.
Correct: Skip Link Before Landmarks
A skip navigation link placed before all landmarks is the one accepted exception to this rule:
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<header>
<h1>Site Name</h1>
</header>
<nav>
<a href="/">Home</a>
</nav>
<main id="main-content">
<h2>Page Content</h2>
<p>This is the main content area.</p>
</main>
<footer>
<p>© 2024 Site Name</p>
</footer>
</body>
The skip link exists outside any landmark, but this is intentional and will not trigger the rule.
When you apply role="img" to an element, you're telling assistive technologies to treat that element as a single image. This is commonly used to group decorative SVG elements, icon fonts, CSS background images, or multiple visual elements into one cohesive image. However, applying the role alone isn't enough — assistive technologies also need a text alternative that describes the image's content or purpose.
Without an accessible name, screen readers will either skip the element entirely or announce it as an unlabeled image, leaving users who rely on assistive technology without critical information. This affects screen reader users (who hear content read aloud), braille display users (who read content through tactile output), and to some extent users with low vision who may use screen readers alongside magnification.
This rule relates to WCAG 2.1 Success Criterion 1.1.1 (Non-text Content), which requires that all non-text content presented to the user has a text alternative that serves the equivalent purpose. It is a Level A requirement — the most fundamental level of accessibility compliance.
How to Fix It
Add an accessible name to any element with role="img" using one of these methods:
aria-label: Provide the text alternative directly as an attribute value.aria-labelledby: Reference the id of another element that contains the descriptive text.title: Use the title attribute as a fallback (though aria-label and aria-labelledby are preferred, as title has inconsistent support across assistive technologies).
The text alternative should be concise and describe the content or purpose of the image. If the image is purely decorative and conveys no information, use aria-hidden="true" instead of role="img" to hide it from assistive technologies entirely.
Examples
Incorrect: role="img" with No Text Alternative
<div role="img">
<!-- SVG icon with no accessible name -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12 2L2 22h20L12 2z"/>
</svg>
</div>
A screen reader encounters this as an image but has no name to announce, so the user gets no useful information.
Correct: Using aria-label
<div role="img" aria-label="Warning: hazardous area ahead">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12 2L2 22h20L12 2z"/>
</svg>
</div>
Correct: Using aria-labelledby
<span id="chart-desc">Quarterly revenue growth from Q1 to Q4 2024</span>
<div role="img" aria-labelledby="chart-desc">
<!-- Complex chart composed of multiple elements -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100">
<rect x="10" y="60" width="30" height="40" fill="#4a90d9"/>
<rect x="50" y="40" width="30" height="60" fill="#4a90d9"/>
<rect x="90" y="25" width="30" height="75" fill="#4a90d9"/>
<rect x="130" y="10" width="30" height="90" fill="#4a90d9"/>
</svg>
</div>
Correct: Using title
<div role="img" title="Company logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
<circle cx="25" cy="25" r="20" fill="#333"/>
</svg>
</div>
Correct: Decorative Image Hidden from Assistive Technology
If the image is purely decorative and adds no meaningful information, hide it instead of labeling it:
<div aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 10">
<line x1="0" y1="5" x2="100" y2="5" stroke="#ccc" stroke-width="1"/>
</svg>
</div>
Note that role="img" is removed here because the element is not intended to be perceived as an image at all.
The scope attribute establishes the relationship between header cells and data cells in a data table. When a screen reader encounters a table, it uses these relationships to announce the relevant header as a user moves between cells. For example, if a column header has scope="col", the screen reader knows that all cells below it in that column are associated with that header. Similarly, scope="row" tells the screen reader that all cells to the right of that header in the same row belong to it.
When scope is used incorrectly — placed on a <td> element in HTML5, applied to a non-table element, or given an invalid value — screen readers may misinterpret the table structure. This primarily affects blind and deafblind users who rely on screen readers for table navigation, as well as users with mobility impairments who may use assistive technologies to navigate structured content. Instead of hearing meaningful header announcements as they move through cells, these users may hear nothing, or worse, hear incorrect header associations that make the data incomprehensible.
This rule is classified as a Deque best practice. Correct use of the scope attribute aligns with the broader goal of making data tables programmatically determinable, which supports WCAG 1.3.1 (Info and Relationships) — the requirement that information and relationships conveyed through presentation can be programmatically determined.
How to fix it
Follow these steps to ensure scope is used correctly:
- Use
scope only on <th> elements. In HTML5, the scope attribute is only valid on <th> elements. If you're using HTML4, it's also permitted on <td>, but this is uncommon and generally unnecessary. - Use only valid values. The
scope attribute should be set to col (for column headers) or row (for row headers). The values colgroup and rowgroup are also valid when headers span groups, but row and col cover the vast majority of use cases. Do not use arbitrary or misspelled values. - Add
scope to all <th> elements. Every <th> in your table should have a scope attribute so screen readers can unambiguously associate headers with their data cells. - Use
<th> for headers and <td> for data. Make sure you're not using <td> for cells that function as headers — use <th> instead, and add the appropriate scope.
Examples
Incorrect: scope on a <td> element (invalid in HTML5)
<table>
<tr>
<td scope="col">Name</td>
<td scope="col">Score</td>
</tr>
<tr>
<td>Alice</td>
<td>95</td>
</tr>
</table>
Here, scope is placed on <td> elements instead of <th>. Screen readers won't recognize these cells as headers.
Incorrect: invalid scope value
<table>
<tr>
<th scope="column">Name</th>
<th scope="column">Score</th>
</tr>
<tr>
<td>Alice</td>
<td>95</td>
</tr>
</table>
The value column is not valid — the correct value is col.
Incorrect: <th> elements without scope
<table>
<tr>
<th>Name</th>
<th>Score</th>
</tr>
<tr>
<th>Alice</th>
<td>95</td>
</tr>
</table>
Although <th> elements are used, none have a scope attribute. Screen readers must guess whether each header applies to a column or a row.
Correct: proper use of scope on <th> elements
<table>
<caption>Student Test Scores</caption>
<tr>
<th scope="col">Name</th>
<th scope="col">Score</th>
</tr>
<tr>
<th scope="row">Alice</th>
<td>95</td>
</tr>
<tr>
<th scope="row">Bob</th>
<td>88</td>
</tr>
</table>
Column headers use scope="col" and row headers use scope="row", all on <th> elements. A screen reader navigating to the cell containing "95" can announce "Name: Alice, Score: 95" — giving the user full context without needing to visually scan the table. The <caption> element provides an accessible name for the table, further improving the experience.
Scrollable regions are created when a container element has CSS overflow set to scroll, auto, or similar values, and its content exceeds the container's visible dimensions. Mouse users can simply scroll these regions with their scroll wheel or by clicking and dragging the scrollbar. However, keyboard-only users need to be able to focus the scrollable region before they can scroll it with arrow keys or other keyboard controls.
Not all browsers automatically place scrollable regions in the tab order. Safari, notably, does not add scrollable <div> elements to the keyboard focus sequence unless they are explicitly made focusable or contain a focusable child. This means that without intervention, keyboard users in affected browsers are completely locked out of the scrollable content — they can see it but have no way to access it.
Who is affected
This issue has a serious impact on several groups of users:
- Keyboard-only users (including people with motor disabilities) cannot scroll to content hidden within the overflow region.
- Blind and deafblind users who rely on screen readers and keyboard navigation may be unable to reach or read content inside the scrollable area.
- Users of alternative input devices that emulate keyboard interaction are similarly blocked.
Related WCAG success criteria
This rule relates to the following WCAG 2.0 / 2.1 / 2.2 Level A success criteria:
- 2.1.1 Keyboard — All functionality must be operable through a keyboard interface without requiring specific timings for individual keystrokes.
- 2.1.3 Keyboard (No Exception) — All functionality must be operable through a keyboard with no exceptions.
Both criteria require that every interactive or content-bearing part of a page be fully usable via keyboard alone.
How to fix it
The most reliable approach is to make the scrollable region itself focusable by adding tabindex="0" to it. This ensures the element appears in the tab order and can receive keyboard focus, allowing the user to scroll with arrow keys.
Alternatively, you can ensure the scrollable region contains at least one focusable element (such as a link, button, or an element with tabindex="0"). If a child element can receive focus, keyboard users can tab into the scrollable region and then use arrow keys or other keyboard shortcuts to scroll.
Important: If the scrollable region contains interactive elements like <input>, <select>, or <textarea>, but all of them have tabindex="-1", the region has no keyboard-accessible entry point and will fail this rule. Either remove the negative tabindex from at least one child, or add tabindex="0" to the scrollable container itself.
When you make a scrollable container focusable, also consider adding an accessible name via aria-label or aria-labelledby so screen reader users understand the purpose of the focusable region. You may also want to add role="region" to help convey its purpose.
Examples
Failing: scrollable region with no focusable elements
All interactive children have tabindex="-1", and the container itself is not focusable. Keyboard users cannot reach or scroll this content.
<div style="height: 100px; overflow: auto;">
<input type="text" tabindex="-1" />
<select tabindex="-1"></select>
<p style="height: 500px;">
This content overflows but is unreachable by keyboard.
</p>
</div>
Failing: scrollable region with only static content
The container has overflow but no focusable element inside, and no tabindex on the container.
<div style="height: 100px; overflow-y: auto;">
<p style="height: 500px;">
Long content that requires scrolling to read fully.
</p>
</div>
Passing: focusable scrollable container
Adding tabindex="0" to the scrollable container allows keyboard users to focus it and scroll with arrow keys. An aria-label and role="region" provide context for screen reader users.
<div
style="height: 100px; overflow-y: auto;"
tabindex="0"
role="region"
aria-label="Scrollable content"
>
<p style="height: 500px;">
Long content that requires scrolling to read fully.
</p>
</div>
Passing: focusable child inside scrollable region
If you prefer not to make the container focusable, ensure at least one child element inside it can receive focus.
<div style="height: 100px; overflow-y: auto;">
<a href="#section1">Jump to section</a>
<p style="height: 500px;">
Long content that requires scrolling to read fully.
</p>
</div>
Conditional: interactive children without negative tabindex
If the scrollable region contains naturally focusable elements (like an <input>) without tabindex="-1", keyboard users can tab into the region. However, be aware that some browsers may intercept keyboard events for autocomplete, which can interfere with scrolling. Adding tabindex="0" directly to the scrollable container is the more robust solution.
<div style="height: 100px; overflow-y: scroll;">
<input type="text" />
<p style="height: 500px;">Additional content below the input.</p>
</div>
A server-side image map is created when an <img> element includes the ismap attribute inside an <a> element. When a user clicks the image, the browser sends the x and y coordinates of the click to the server, which then determines what action to take. This approach has two critical accessibility failures:
No keyboard access. Because the interaction depends on mouse coordinates, there is no way for keyboard-only users to activate specific regions of the map. Keyboard users cannot generate coordinate-based clicks, so every link within the image map becomes unreachable.
No text alternatives for individual regions. Screen readers cannot identify or announce the distinct clickable areas within a server-side image map. Unlike client-side image maps, where each <area> element can have an alt attribute describing its purpose, server-side image maps offer no mechanism to label individual hotspots. This leaves blind and deafblind users unable to understand or interact with the content.
Users with mobility impairments who rely on switch devices or other keyboard-based assistive technologies are also affected, as these tools cannot produce the precise mouse coordinates that server-side image maps require.
This rule relates to WCAG 2.1 / 2.2 Success Criterion 2.1.1 (Keyboard) at Level A, which requires that all functionality be operable through a keyboard interface. It also aligns with Section 508 §1194.22(f), which explicitly states that client-side image maps must be provided instead of server-side image maps except where regions cannot be defined with an available geometric shape.
How to Fix
Replace every server-side image map with a client-side image map:
- Remove the
ismap attribute from the <img> element. - Remove the wrapping
<a> element (if it was only used for the server-side map). - Add a
usemap attribute to the <img> element, referencing a <map> element by name. - Define each clickable region using
<area> elements inside the <map>, specifying the shape, coords, href, and alt attributes for each one. - Ensure every
<area> has a meaningful alt attribute describing the link destination.
Examples
Incorrect: Server-side image map
The ismap attribute makes this a server-side image map. Keyboard users cannot access the links, and no text alternatives exist for individual regions.
<a href="/maps/nav.map">
<img src="/images/navbar.gif" alt="Navigation bar" ismap>
</a>
Correct: Client-side image map
Each clickable region is defined with an <area> element that has its own alt text and href. Keyboard users can tab through the areas, and screen readers can announce each link.
<img
src="/images/solar_system.jpg"
alt="Solar System"
width="472"
height="800"
usemap="#solar-system-map">
<map name="solar-system-map">
<area
shape="rect"
coords="115,158,276,192"
href="https://en.wikipedia.org/wiki/Mercury_(planet)"
alt="Mercury">
<area
shape="rect"
coords="115,193,276,234"
href="https://en.wikipedia.org/wiki/Venus"
alt="Venus">
<area
shape="rect"
coords="115,235,276,280"
href="https://en.wikipedia.org/wiki/Earth"
alt="Earth">
</map>
Correct: Simple navigation using a client-side image map
<img
src="/images/navbar.gif"
alt="Site navigation"
width="600"
height="50"
usemap="#nav-map">
<map name="nav-map">
<area
shape="rect"
coords="0,0,150,50"
href="/home"
alt="Home">
<area
shape="rect"
coords="150,0,300,50"
href="/products"
alt="Products">
<area
shape="rect"
coords="300,0,450,50"
href="/about"
alt="About us">
<area
shape="rect"
coords="450,0,600,50"
href="/contact"
alt="Contact">
</map>
In the client-side examples, each <area> is focusable via keyboard, has a descriptive alt attribute for screen readers, and links directly to its destination without requiring server-side coordinate processing. If your image map regions are complex and cannot be represented with the available shape values (rect, circle, poly), consider replacing the image map entirely with a set of individual links or buttons, which provide even better accessibility.
Screen readers and keyboard-only navigation process page content sequentially, following the DOM order. This means that everything before the main content — site logos, navigation bars, search forms, utility links — must be traversed before a user reaches what they actually came for. On complex sites, this repetitive content can be extremely lengthy.
A properly functioning skip link gives these users a shortcut. When the skip link's target is missing or not focusable, activating the link does nothing. The user remains at the top of the page with no indication of what went wrong, which is both confusing and frustrating.
This issue primarily affects:
- Blind and deafblind users who rely on screen readers to navigate sequentially.
- Keyboard-only users (including those with mobility impairments) who tab through every interactive element.
- Sighted keyboard users who benefit from the visual skip link appearing on focus.
This rule is a Deque best practice and aligns with WCAG technique G1 ("Adding a link at the top of each page that goes directly to the main content area"), which supports WCAG 2.4.1 Bypass Blocks (Level A). Success Criterion 2.4.1 requires that a mechanism exists to bypass blocks of content repeated across multiple pages.
How to Fix It
- Add a skip link as the very first focusable element inside the
<body> tag, before any navigation or repeated content. - Set the
href to a fragment identifier (e.g., #main-content) that matches the id of the target element. - Ensure the target element exists in the DOM with the corresponding
id. - Make the target focusable if it is not natively focusable. Non-interactive elements like
<div> or <main> need tabindex="-1" so the browser can move focus to them when the skip link is activated. - Do not hide the skip link with
display: none or visibility: hidden, as these make the link inaccessible to all users, including screen reader users.
Handling the Skip Link's Visibility
You can make the skip link visually hidden until it receives keyboard focus. This approach keeps the layout clean for mouse users while remaining fully accessible to keyboard users:
- Use CSS to position the link off-screen by default.
- On
:focus, bring it back on-screen so sighted keyboard users can see it.
Alternatively, you can make the skip link permanently visible — this is the simplest and most inclusive approach.
Important: Never use display: none or visibility: hidden on the skip link. These properties remove the element from the accessibility tree and the tab order, making it useless for everyone.
Examples
Incorrect: Skip Link Target Does Not Exist
The skip link points to #main-content, but no element with that id exists on the page.
<body>
<a href="#main-content">Skip to main content</a>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
<main>
<h1>Welcome</h1>
<p>Page content goes here.</p>
</main>
</body>
Incorrect: Target Exists but Is Not Focusable in Some Browsers
Some older or WebKit-based browsers may not move focus to a non-interactive element even with a matching id, unless tabindex="-1" is added.
<body>
<a href="#main-content">Skip to main content</a>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<div id="main-content">
<h1>Welcome</h1>
<p>Page content goes here.</p>
</div>
</body>
Correct: Skip Link with a Focusable Target
The target element has both a matching id and tabindex="-1" to ensure it receives focus reliably across all browsers.
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
<main id="main-content" tabindex="-1">
<h1>Welcome</h1>
<p>Page content goes here.</p>
</main>
</body>
Correct: Skip Link That Is Visually Hidden Until Focused
This CSS pattern hides the skip link off-screen by default and reveals it when the user tabs to it.
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<main id="main-content" tabindex="-1">
<h1>Welcome</h1>
<p>Page content goes here.</p>
</main>
</body>
.skip-link {
position: absolute;
left: -9999px;
top: auto;
width: 1px;
height: 1px;
overflow: hidden;
}
.skip-link:focus {
position: static;
width: auto;
height: auto;
overflow: visible;
padding: 8px 16px;
background: #ffc;
border: 2px solid #990000;
z-index: 1000;
}
What the Rule Checks
This axe-core rule (skip-link) verifies that every skip link on the page — typically the first link in the document — has a target that both exists in the DOM and is focusable. If the href points to a fragment identifier like #main-content, the rule confirms that an element with id="main-content" is present and can receive focus.
The <summary> element serves as a built-in disclosure widget in HTML. It functions like a button: users click or activate it to expand or collapse the associated <details> content. Because it's an interactive control, it must communicate its purpose to all users, including those who rely on assistive technology.
When a <summary> element has no accessible name — for example, when it's empty or contains only a non-text element without an alternative — screen readers will announce it as something generic like "disclosure triangle" or simply "button" with no label. This leaves blind and deafblind users unable to understand what the control does or what information it reveals. They may skip it entirely, missing potentially important content.
This rule maps to WCAG 2.0, 2.1, and 2.2 Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that all user interface components have a programmatically determinable name. It also applies under Section 508, Trusted Tester guidelines, and EN 301 549 (9.4.1.2). The user impact is considered serious because it directly blocks access to content for assistive technology users.
How to fix it
The simplest and most reliable approach is to place descriptive text directly inside the <summary> element. This text should clearly indicate the topic or purpose of the hidden content.
If the <summary> element cannot contain visible text — for instance, when it uses a CSS background image or an icon — you can provide a hidden accessible name using one of these methods:
aria-label: Provide the accessible name directly as an attribute value.aria-labelledby: Point to another element's id that contains the accessible name text.title: Supply a tooltip that also serves as the accessible name. Note that title is the least reliable option, as it's not consistently exposed across all assistive technologies.
Note that the summary-name rule is separate from the button-name rule because <summary> elements have different inherent semantics than <button> elements, even though both are interactive controls.
Examples
Failing: empty <summary> element
This <summary> has no text or accessible name, so screen readers cannot convey its purpose.
<details>
<summary></summary>
<p>Returns must be initiated within 30 days of purchase.</p>
</details>
Failing: <summary> with only a non-text element and no alternative
The image inside the <summary> has no alt text, so there is still no accessible name.
<details>
<summary>
<img src="info-icon.png">
</summary>
<p>Returns must be initiated within 30 days of purchase.</p>
</details>
Passing: <summary> with visible text
The most straightforward fix — place clear, descriptive text inside the <summary>.
<details>
<summary>Return policy</summary>
<p>Returns must be initiated within 30 days of purchase.</p>
</details>
Passing: <summary> with an image that has alt text
When using an image inside <summary>, the alt attribute provides the accessible name.