Guias de acessibilidade para mobilidade
Aprenda como identificar e corrigir problemas de acessibilidade comuns sinalizados pelo Axe Core — para que suas páginas sejam inclusivas e utilizáveis por todos. Consulte também o nosso Guias de validação HTML.
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
metarefresh 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
301or302redirect 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>
HTML has a clear content model: interactive elements like <button>, <a>, <select>, and <input> are not allowed to contain other interactive or focusable elements. When you nest one interactive control inside another — for example, placing a link inside a button — browsers and assistive technologies handle the situation inconsistently and unpredictably.
Screen readers are especially affected. When a user tabs to a nested interactive element, the screen reader may produce an empty or silent tab stop. The inner control’s name, role, and state are not announced, meaning the user has no idea what the control does or that it even exists. This creates a serious barrier for blind users who rely on screen readers and keyboard-only users who navigate by tabbing through focusable elements.
This issue also affects users with mobility impairments who use switch devices or other assistive input methods that depend on an accurate understanding of the interactive elements on the page.
Related WCAG Success Criteria
This rule maps to WCAG 2.0, 2.1, and 2.2 Success Criterion 4.1.2: Name, Role, Value (Level A). This criterion requires that for all user interface components, the name and role can be programmatically determined, and states, properties, and values that can be set by the user can be programmatically set. Nested interactive controls violate this because the inner control’s name and role become inaccessible to assistive technology.
How to Fix It
The fix is straightforward: do not place focusable or interactive elements inside other interactive elements. Here are common strategies:
- Separate the controls. Place interactive elements side by side rather than nesting them.
- Restructure the layout. If a design requires a clickable card with inner links or buttons, use CSS positioning or JavaScript event delegation rather than literal nesting.
-
Check elements with ARIA roles. A
<div>withrole="button"is treated as an interactive control. It must not contain links, buttons, inputs, or any other focusable elements.
Examples
Incorrect: Link Nested Inside a Button
The <a> element inside the <button> creates a nested interactive control that screen readers will not announce properly.
<button>
Save
<a href="/options">More options</a>
</button>
Incorrect: Link Nested Inside an Element with role="button"
Even though the outer element is a <div>, the role="button" makes it an interactive control. The nested <a> is inaccessible to screen readers.
<div role="button" tabindex="0">
Search
<a href="/settings">Settings</a>
</div>
Incorrect: Button Nested Inside a Link
Placing a <button> inside an <a> element is equally problematic.
<a href="/dashboard">
Go to dashboard
<button>Settings</button>
</a>
Correct: Separate Interactive Controls
Place each interactive element independently so both are fully announced and operable.
<button>Save</button>
<a href="/options">More options</a>
Correct: Clickable Card with Separate Controls
If you need a card-like pattern where the entire card is clickable but also contains separate actions, avoid literal nesting. Use a flat structure with CSS for layout.
<div class="card">
<h3><a href="/article/1">Article Title</a></h3>
<p>A short description of the article.</p>
<button>Bookmark</button>
</div>
Correct: Single Button with No Nested Interactive Content
A simple, properly structured button with only non-interactive content inside it.
<button>
<svg aria-hidden="true" focusable="false">
<use href="#icon-save"></use>
</svg>
Save
</button>
Note that in the example above, the <svg> element has focusable="false" to prevent it from being a tab stop in some browsers (notably older versions of Internet Explorer and Edge), and aria-hidden="true" because the button text already provides the accessible name.
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 increasesfont-size, appliesfont-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.
The role="none" and role="presentation" attributes tell browsers to strip the semantic meaning from an element, effectively removing it from the accessibility tree. This is useful when elements are used purely for visual layout and carry no meaningful content for assistive technology users.
However, the WAI-ARIA specification defines specific conditions under which this removal is overridden. If a presentational element has a global ARIA attribute (such as aria-hidden, aria-label, aria-live, aria-describedby, etc.) or is focusable (either natively, like a <button>, or through tabindex), the browser must ignore the presentational role and keep the element in the accessibility tree. This is known as a presentational role conflict.
When this conflict occurs, screen reader users may encounter elements that were intended to be hidden but are instead announced — potentially with confusing or missing context. This creates a disorienting experience, particularly for blind users and users with low vision who rely on screen readers to understand the page structure.
This rule is flagged as a Deque best practice. While not mapped to a specific WCAG success criterion, it supports the broader goal of ensuring the accessibility tree accurately represents the author’s intent, which contributes to a coherent experience under WCAG principles like 1.3.1 Info and Relationships and 4.1.2 Name, Role, Value.
How to Fix It
For every element with role="none" or role="presentation", ensure that:
-
No global ARIA attributes are present. Remove attributes like
aria-hidden,aria-label,aria-live,aria-describedby,aria-atomic, and any other global ARIA attributes. -
The element is not focusable. Don’t use natively focusable elements (like
<button>,<a href>, or<input>) with a presentational role. Also, don’t addtabindexto a presentational element.
If the element genuinely needs a global ARIA attribute or needs to be focusable, then it shouldn’t have a presentational role — remove role="none" or role="presentation" instead.
Examples
Incorrect: Presentational element with a global ARIA attribute
The aria-hidden="true" attribute is a global ARIA attribute, which creates a conflict with role="none":
<li role="none" aria-hidden="true">Decorative item</li>
Incorrect: Natively focusable element with a presentational role
A <button> is natively focusable, so its presentational role will be ignored by the browser:
<button role="none">Click me</button>
Incorrect: Presentational element made focusable via tabindex
Adding tabindex="0" makes the element focusable, overriding the presentational role:
<img alt="" role="presentation" tabindex="0">
Correct: Presentational element with no conflicts
These elements have no global ARIA attributes and are not focusable, so they will be properly removed from the accessibility tree:
<li role="none">Layout item</li>
<li role="presentation">Layout item</li>
<img alt="" role="presentation">
Correct: Removing the presentational role when focus is needed
If the element needs to be focusable, remove the presentational role and give it an appropriate accessible name instead:
<button aria-label="Submit form">Submit</button>
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.
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>
When a <select> element lacks an accessible name, screen readers announce it as something generic like “combobox” or “listbox” with no context about what it represents. A sighted user might see nearby text like “Country” and understand the purpose, but that visual proximity means nothing to assistive technology unless a programmatic relationship exists between the text and the form control.
This issue critically affects users who are blind, have low vision, or have mobility impairments and rely on assistive technologies to interact with forms. Without a proper label, these users cannot determine what data a dropdown expects, making form completion error-prone or impossible.
Related WCAG Success Criteria
This rule maps to WCAG 2.0, 2.1, and 2.2 Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that all user interface components have a name that can be programmatically determined. It also falls under Section 508 §1194.22(n), which mandates that online forms allow people using assistive technology to access all field elements, directions, and cues needed for completion.
How to Fix It
There are several ways to give a <select> element an accessible name, listed here from most preferred to least preferred:
-
Explicit
<label>withfor/id— The most common and recommended approach. Use theforattribute on the<label>to match theidof the<select>. This also gives sighted users a larger click target. -
Implicit
<label>wrapping — Wrap the<select>inside a<label>element. Nofor/idpairing is needed. -
aria-labelledby— Point to theidof an existing visible text element that serves as the label. Useful when a traditional<label>would break layout or when multiple elements combine to form the label. -
aria-label— Provide an invisible text label directly on the<select>. Use this only when no visible label text exists or is appropriate.
Whichever method you choose, make sure:
- The label text clearly describes what the user should select.
-
Each
idattribute is unique on the page. -
Each
<select>has only one labeling method to avoid conflicts or confusion.
Examples
Incorrect: No Label Association
This places text near the <select> but creates no programmatic link between them. Screen readers will not announce “State” when the user focuses the dropdown.
State:
<select>
<option value="ny">New York</option>
<option value="ca">California</option>
</select>
Correct: Explicit <label> with for and id
The for attribute on the <label> matches the id on the <select>, creating a clear programmatic association.
<label for="state">State:</label>
<select id="state">
<option value="ny">New York</option>
<option value="ca">California</option>
</select>
Correct: Implicit <label> Wrapping
Wrapping the <select> inside the <label> element implicitly associates them.
<label>
State:
<select>
<option value="ny">New York</option>
<option value="ca">California</option>
</select>
</label>
Correct: Using aria-labelledby
When visible text already exists elsewhere (e.g., in a heading or table cell), use aria-labelledby to reference it by id.
<span id="state-label">State:</span>
<select aria-labelledby="state-label">
<option value="ny">New York</option>
<option value="ca">California</option>
</select>
Correct: Using aria-label
When no visible label is present or appropriate, aria-label provides an accessible name directly. Note that this label is invisible to sighted users, so only use it when context is already visually clear.
<select aria-label="State">
<option value="ny">New York</option>
<option value="ca">California</option>
</select>
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 analtattribute 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
ismapattribute from the<img>element. -
Remove the wrapping
<a>element (if it was only used for the server-side map). -
Add a
usemapattribute to the<img>element, referencing a<map>element by name. -
Define each clickable region using
<area>elements inside the<map>, specifying theshape,coords,href, andaltattributes for each one. -
Ensure every
<area>has a meaningfulaltattribute 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
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.
When an <svg> element is given a semantic role like img, graphics-document, or graphics-symbol, it signals to assistive technologies that the element conveys meaningful content — just like an <img> tag would. However, unlike <img> elements that have a straightforward alt attribute, SVG elements require different techniques to provide their accessible name. If no text alternative is supplied, screen reader users will encounter the SVG as an unlabeled graphic, losing access to whatever information it communicates.
This issue affects people who are blind, deafblind, or who use screen readers for other reasons. It can also impact users of braille displays and other assistive technologies that rely on programmatically determined text to represent non-text content.
Related WCAG Success Criteria
This rule maps to WCAG 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 the equivalent purpose. This criterion applies across WCAG 2.0, 2.1, and 2.2, and is also required by Section 508 and EN 301 549.
Because this is a Level A requirement, it represents the most fundamental level of accessibility. Failing to meet it means some users will have no way to understand the content your SVG conveys.
How to Fix It
There are several ways to give an <svg> element an accessible text alternative. Use one of the following approaches:
Use a <title> child element
The <title> element must be a direct child of the <svg> element. It provides a short text description that assistive technologies can read. If there are multiple <title> children, the first one must contain text — only the first <title> is used.
Use the aria-label attribute
Add an aria-label attribute directly to the <svg> element with a concise description of the graphic’s content.
Use the aria-labelledby attribute
Reference one or more existing elements on the page by their id values using aria-labelledby. This is especially useful when the description text is already visible elsewhere on the page.
Use the title attribute
The title attribute on the <svg> element can also provide an accessible name, though <title> child elements or ARIA attributes are generally preferred for better screen reader support.
How the Rule Works
The axe rule checks that the <svg> element with a role of img, graphics-document, or graphics-symbol has a computable accessible name. Specifically, it looks for:
-
A direct
<title>child element with non-empty text content -
An
aria-labelattribute with a non-empty value -
An
aria-labelledbyattribute that references elements with text content
The check fails if:
-
There is no
<title>child and no ARIA labeling attribute -
The
<title>child is empty or contains only whitespace -
The
<title>element is a grandchild (nested inside another element within the SVG) rather than a direct child -
There are multiple
<title>elements and the first one is empty
Examples
Failing — SVG with role="img" and no text alternative
<svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
Screen readers will announce this as an image but cannot describe its content.
Failing — Empty <title> element
<svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<title></title>
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
Failing — <title> is a grandchild, not a direct child
<svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g>
<title>A blue circle</title>
<circle cx="50" cy="50" r="40" fill="blue" />
</g>
</svg>
The <title> must be a direct child of the <svg> element itself.
Failing — First <title> is empty
<svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<title></title>
<title>A blue circle</title>
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
Only the first <title> child is evaluated. Since it is empty, the rule fails even though the second one has text.
Passing — Using a <title> child element
<svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<title>A blue circle</title>
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
Passing — Using aria-label
<svg role="img" aria-label="A blue circle" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
Passing — Using aria-labelledby
<h2 id="chart-heading">Monthly Sales Data</h2>
<svg role="img" aria-labelledby="chart-heading" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100">
<rect x="10" y="50" width="30" height="50" fill="green" />
<rect x="50" y="20" width="30" height="80" fill="green" />
</svg>
Passing — Decorative SVG excluded from the accessibility tree
If an SVG is purely decorative and conveys no meaning, you can hide it from assistive technologies instead of adding a text alternative:
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="blue" />
</svg>
Note that this approach removes the role="img" and adds aria-hidden="true", so the rule no longer applies. Only use this for truly decorative graphics — if the SVG conveys any information, it needs a text alternative.
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.
When interactive elements on a page are too small or too close together, users frequently activate the wrong control. This is frustrating at best and can cause serious problems — like submitting a form prematurely, navigating to the wrong page, or triggering an unintended action that’s difficult to undo.
This rule relates to WCAG 2.2 Success Criterion 2.5.8: Target Size (Minimum) at the AA level. The criterion requires that touch targets be at least 24 by 24 CSS pixels, measured by the largest unobscured area of the target. If a target is smaller than 24 by 24 pixels, it can still pass if there is enough space around it — specifically, you must be able to draw a virtual 24-pixel-diameter circle centered on the target that does not intersect any other target or the circle of any other undersized target.
Who is affected
A wide range of users benefit from properly sized touch targets:
- People using mobile devices where touch is the primary interaction method
- Users with motor impairments such as hand tremors, limited dexterity, or reduced fine motor control
- People using devices in unstable environments like public transportation or while walking
- Users operating devices one-handed or with limited grip
- People with large fingers or those who interact with the screen using a knuckle or part of a finger
- Mouse and stylus users who struggle with precise targeting
Even users without disabilities can be affected — anyone tapping a tiny button on a phone screen has experienced this problem.
How the rule works
The axe engine checks each interactive touch target on the page by:
- Calculating the largest unobscured area of the target (the portion not visually covered by other elements).
- Checking if that area is at least 24 by 24 CSS pixels.
- If the target is undersized, checking whether a virtual 24-pixel-diameter circle centered on the target overlaps with any other target or with the circle of any other undersized target.
If the target fails both the size check and the spacing check, the rule reports a violation.
How to fix it
You have two options:
-
Make the target at least 24 × 24 CSS pixels. Increase the element’s size through
font-size,padding,min-width,min-height, or any combination that results in at least 24 × 24 pixels of tappable area. - Add sufficient spacing. If the target must remain small, ensure there is enough margin or space around it so that the 24-pixel circle centered on the target doesn’t overlap with neighboring targets.
Keep in mind that padding increases the clickable area of an element, while margin adds spacing between elements. Both strategies can help you meet this requirement.
Examples
Incorrect: small target too close to another target
The + button renders very small, and the negative margin pulls the adjacent button into its space, making both targets nearly impossible to activate accurately.
<button id="target">+</button>
<button style="margin-left: -10px">Adjacent Target</button>
Correct: target with sufficient size
Using a larger font-size and padding ensures the button meets the 24 × 24 pixel minimum.
<button style="font-size: 24px; padding: 4px 8px;">Submit</button>
Correct: small target with sufficient spacing
If a small target is necessary, adding enough margin ensures the 24-pixel circle around it doesn’t overlap with adjacent targets.
<button>+</button>
<button style="margin-left: 24px">Adjacent Target</button>
Correct: icon buttons with explicit sizing
For icon buttons that might otherwise render very small, set explicit dimensions and use padding to enlarge the touch area.
<button style="min-width: 24px; min-height: 24px; padding: 4px;" aria-label="Close">
✕
</button>
Correct: inline links with sufficient spacing
When links appear in close proximity, such as in a list, ensure each item has enough height or spacing.
<ul style="list-style: none; padding: 0;">
<li style="padding: 4px 0;"><a href="/home">Home</a></li>
<li style="padding: 4px 0;"><a href="/about">About</a></li>
<li style="padding: 4px 0;"><a href="/contact">Contact</a></li>
</ul>
Note that WCAG 2.5.8 includes several exceptions, such as inline links within text, targets whose size is determined by the user agent and not modified by the author, and cases where a specific presentation is legally required. Refer to Understanding Success Criterion 2.5.8: Target Size (Minimum) for the full list of exceptions and additional guidance.
Pronto para validar os seus sites?
Comece o seu teste gratuito hoje.