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.
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> |
banner |
Site-wide header (when not nested inside <article> or <section>) |
<nav> |
navigation |
Navigation links |
<main> |
main |
Primary page content |
<footer> |
contentinfo |
Site-wide footer (when not nested inside <article> or <section>) |
<aside> |
complementary |
Supporting content |
<section> (with accessible name) |
region |
A distinct section of the page |
<form> (with accessible name) |
form |
A 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.
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
scopeonly on<th>elements. In HTML5, thescopeattribute 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
scopeattribute should be set tocol(for column headers) orrow(for row headers). The valuescolgroupandrowgroupare also valid when headers span groups, butrowandcolcover the vast majority of use cases. Do not use arbitrary or misspelled values. -
Add
scopeto all<th>elements. Every<th>in your table should have ascopeattribute 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 appropriatescope.
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.
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
hrefto a fragment identifier (e.g.,#main-content) that matches theidof 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>needtabindex="-1"so the browser can move focus to them when the skip link is activated. -
Do not hide the skip link with
display: noneorvisibility: 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 tabindex attribute controls whether and in what order an element receives keyboard focus. When set to a positive integer (e.g., tabindex="1", tabindex="5", or even tabindex="100"), the browser tabs to that element before any elements without a tabindex or with tabindex="0". This means the tab order no longer follows the natural reading order of the page, which is the order elements appear in the DOM.
Why This Is a Problem
Keyboard-only users, screen reader users, and people with motor disabilities rely on a logical, predictable tab order to navigate a page. A positive tabindex breaks that predictability in several ways:
- Unexpected tab order: The user is pulled to elements out of their visual and logical sequence, causing confusion and disorientation.
-
Elements appear to be skipped: Because positive-
tabindexelements are visited first, when the user later reaches their position in the document, the browser skips them since they were already visited. Users may not realize they need to cycle through the entire page to reach those elements again. -
Cascading maintenance problems: If you set
tabindex="1"on one element, you must then set explicittabindexvalues on every focusable element between it and wherever you want tab order to resume — potentially dozens or hundreds of elements. This is fragile and nearly impossible to maintain.
Even a single element with tabindex="100" will be tabbed to first on the page, regardless of how many focusable elements exist. The number doesn’t represent a position among 100 items; it simply means “before everything with a lower or no tabindex.”
This rule is classified as a Deque Best Practice and addresses usability for people who are blind, deafblind, or have mobility disabilities. While not mapped to a specific WCAG success criterion, it directly supports the principles behind WCAG 2.4.3 Focus Order (Level A), which requires that the navigation order of focusable components be meaningful and operable.
How to Fix It
There are three approaches, depending on your situation:
1. Change tabindex to 0
Setting tabindex="0" places the element in the natural tab order based on its position in the DOM. This is the simplest fix, though you should verify that the resulting order still makes sense.
2. Remove tabindex and restructure the HTML
Native interactive elements like links (<a>), buttons (<button>), and form controls (<input>, <select>, <textarea>) are already in the tab order by default. If you find yourself needing a positive tabindex to “fix” the order, the real solution is usually to rearrange the elements in the source code so the DOM order matches the intended navigation sequence.
Keep in mind that certain CSS properties can make the visual order differ from the DOM order, leading to confusing tab behavior:
-
position: absoluteandposition: relative -
float: leftandfloat: right -
orderin Flexbox or Grid layouts
Always ensure that DOM order = visual order = tab order.
3. Use tabindex="-1" with JavaScript
Setting tabindex="-1" removes the element from the tab order entirely but still allows it to receive focus programmatically via JavaScript (e.g., element.focus()). This is useful for complex interactive widgets where you need to manage focus dynamically, such as within tab panels, menus, or modal dialogs.
Examples
Incorrect: Positive tabindex values
<a href="/home" tabindex="3">Home</a>
<a href="/about" tabindex="1">About</a>
<a href="/contact" tabindex="2">Contact</a>
<a href="/blog">Blog</a>
In this example, the tab order would be: About → Contact → Home → Blog. This does not match the visual order, which is confusing for keyboard users.
Correct: Natural tab order with no tabindex
<a href="/about">About</a>
<a href="/contact">Contact</a>
<a href="/home">Home</a>
<a href="/blog">Blog</a>
Here, the HTML source order matches the desired tab order. No tabindex is needed.
Correct: Using tabindex="0" for non-interactive elements
<div role="button" tabindex="0" aria-label="Toggle menu">
☰
</div>
Using tabindex="0" places this custom button in the natural tab order without disrupting the sequence of other focusable elements.
Correct: Using tabindex="-1" for programmatic focus
<div role="tabpanel" id="panel-1" tabindex="-1">
<p>Panel content goes here.</p>
</div>
<script>
// Focus the panel when its corresponding tab is activated
document.getElementById('panel-1').focus();
</script>
The panel is not reachable via the Tab key during normal navigation, but JavaScript can move focus to it when appropriate — such as when a user activates a tab control.
Screen readers announce both the <caption> and summary when a user encounters a data table. The <caption> element serves as the table’s visible, on-screen title — it tells all users what the table is about. The summary attribute (available in HTML4 and XHTML, though deprecated in HTML5) is read only by screen readers and is intended to describe the table’s structure, such as how rows and columns are organized. When these two contain the same text, the screen reader simply reads the same content twice, which provides no additional value and creates a confusing, redundant experience.
This issue primarily affects blind and deafblind users who rely on screen readers to navigate and interpret tabular data. Screen readers have specialized table navigation features, but they depend on accurate, meaningful markup to function properly. When the caption and summary are duplicated, users lose the opportunity to get both a concise title and a helpful structural description of the table.
This rule is identified as a Deque best practice and is also referenced in the RGAA (French government accessibility standard). While it doesn’t map directly to a specific WCAG success criterion, it supports the principles behind WCAG 1.3.1 Info and Relationships (Level A), which requires that information and structure conveyed visually are also available programmatically.
How to Fix It
The fix is straightforward: make sure the <caption> and summary attribute contain different text that each serves its intended purpose.
-
Use the
<caption>element to give the table a concise, visible title that describes what data the table contains. -
Use the
summaryattribute to describe the table’s structure — for example, how many column groups there are, what the row and column headers represent, or how the data is organized.
If you find you can’t think of distinct content for both, consider whether you actually need the summary attribute at all. For simple tables, a <caption> alone is often sufficient. Note that summary is deprecated in HTML5, so for modern documents you may want to provide structural descriptions using other techniques, such as a paragraph linked with aria-describedby.
Examples
Incorrect: Identical Caption and Summary
The summary and <caption> contain the same text, causing screen readers to repeat the information.
<table summary="Quarterly sales figures">
<caption>Quarterly sales figures</caption>
<tr>
<th>Quarter</th>
<th>Revenue</th>
</tr>
<tr>
<td>Q1</td>
<td>$50,000</td>
</tr>
<tr>
<td>Q2</td>
<td>$62,000</td>
</tr>
</table>
Correct: Caption and Summary Provide Different Information
The <caption> names the table, while the summary describes its structure.
<table summary="Column 1 lists each quarter, column 2 shows total revenue for that quarter.">
<caption>Quarterly sales figures</caption>
<tr>
<th>Quarter</th>
<th>Revenue</th>
</tr>
<tr>
<td>Q1</td>
<td>$50,000</td>
</tr>
<tr>
<td>Q2</td>
<td>$62,000</td>
</tr>
</table>
Correct: Using Only a Caption (HTML5 Approach)
For simple tables in HTML5, you can skip the deprecated summary attribute entirely and rely on a <caption> alone.
<table>
<caption>Quarterly sales figures</caption>
<tr>
<th>Quarter</th>
<th>Revenue</th>
</tr>
<tr>
<td>Q1</td>
<td>$50,000</td>
</tr>
<tr>
<td>Q2</td>
<td>$62,000</td>
</tr>
</table>
Correct: HTML5 Alternative Using aria-describedby
If you need to provide a structural description in HTML5, use aria-describedby to link to a nearby paragraph instead of using summary.
<p id="table-desc">Column 1 lists each quarter, column 2 shows total revenue for that quarter.</p>
<table aria-describedby="table-desc">
<caption>Quarterly sales figures</caption>
<tr>
<th>Quarter</th>
<th>Revenue</th>
</tr>
<tr>
<td>Q1</td>
<td>$50,000</td>
</tr>
<tr>
<td>Q2</td>
<td>$62,000</td>
</tr>
</table>
Ready to validate your sites?
Start your free trial today.