Accessibility Guides for blind
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.
When you add an ARIA attribute to an HTML element, the browser exposes that information through the accessibility tree so assistive technologies like screen readers can interpret it. If the attribute name is invalid — whether due to a typo like aria-hiden instead of aria-hidden, or a fabricated attribute like aria-visible that doesn't exist in the spec — the browser won't recognize it. The attribute is effectively dead code, and the accessibility enhancement you intended never reaches the user.
This is classified as a critical issue because the consequences can be severe. For example, if you misspell aria-required as aria-requried on a form field, screen reader users won't be informed that the field is mandatory. If you misspell aria-expanded on a disclosure widget, blind and deafblind users won't know whether a section is open or closed. Keyboard-only users who rely on screen readers are also affected when interactive states and properties fail to communicate correctly.
Related WCAG Success Criteria
This rule maps to WCAG Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that for all user interface components, the name, role, and states/properties can be programmatically determined. Invalid ARIA attributes fail to communicate states and properties to assistive technologies, directly violating this criterion. This applies across WCAG 2.0, 2.1, and 2.2, as well as EN 301 549 (guideline 9.4.1.2).
How to Fix It
- Audit your ARIA attributes. Review every attribute in your markup that starts with
aria-and confirm it matches a valid attribute name from the WAI-ARIA specification. - Check for typos. Common mistakes include
aria-labelled-by(correct:aria-labelledby),aria-hiden(correct:aria-hidden), andaria-discribedby(correct:aria-describedby). - Remove invented attributes. Attributes like
aria-visible,aria-tooltip, oraria-icondo not exist in the WAI-ARIA spec and will have no effect. - Use tooling. IDE extensions, linters (like
eslint-plugin-jsx-a11y), and the axe DevTools browser extension can catch invalid ARIA attribute names during development.
Common Valid ARIA Attributes
Here are some frequently used ARIA attributes for reference:
- Widget attributes:
aria-checked,aria-disabled,aria-expanded,aria-hidden,aria-label,aria-pressed,aria-readonly,aria-required,aria-selected,aria-valuenow - Live region attributes:
aria-live,aria-atomic,aria-relevant,aria-busy - Relationship attributes:
aria-labelledby,aria-describedby,aria-controls,aria-owns,aria-flowto - Drag-and-drop attributes:
aria-dropeffect,aria-grabbed
Examples
Incorrect: Misspelled ARIA Attribute
<button aria-expandd="false">Show details</button>
The attribute aria-expandd is not a valid ARIA attribute. Screen readers will not announce the expanded/collapsed state of this button.
Incorrect: Non-Existent ARIA Attribute
<div aria-visible="true">Important announcement</div>
The attribute aria-visible does not exist in the WAI-ARIA specification. It will be completely ignored by assistive technologies.
Incorrect: Typo in a Relationship Attribute
<input type="text" aria-discribedby="help-text">
<p id="help-text">Enter your full name as it appears on your ID.</p>
The attribute aria-discribedby is a misspelling of aria-describedby. The input will not be associated with the help text for screen reader users.
Correct: Properly Spelled ARIA Attributes
<button aria-expanded="false">Show details</button>
<div aria-hidden="true">Decorative content</div>
<input type="text" aria-describedby="help-text">
<p id="help-text">Enter your full name as it appears on your ID.</p>
Each of these examples uses a valid, correctly spelled ARIA attribute that browsers and assistive technologies will recognize and process as intended.
Why This Matters
The autocomplete attribute does more than enable browser autofill — it programmatically communicates the purpose of a form field to assistive technologies. This information is critical for several groups of users:
- Screen reader users rely on the announced field purpose to understand what information is being requested. Without a valid
autocompletevalue, the screen reader may not convey this context clearly. - Users with cognitive disabilities benefit from browsers and assistive tools that can auto-populate fields or display familiar icons based on the field's purpose, reducing the mental effort required to complete forms.
- Users with mobility impairments benefit from autofill functionality that minimizes the amount of manual input required.
- Users with low vision may use personalized stylesheets or browser extensions that adapt the presentation of form fields based on their declared purpose (e.g., showing a phone icon next to a telephone field).
This rule maps to WCAG 2.1 Success Criterion 1.3.5: Identify Input Purpose (Level AA), which requires that the purpose of input fields collecting user information can be programmatically determined. The autocomplete attribute is the standard mechanism for satisfying this requirement in HTML.
How the Rule Works
The axe rule autocomplete-valid checks that:
- The
autocompleteattribute value is a valid token (or combination of tokens) as defined in the HTML specification for autofill. - The value is appropriate for the type of form control it is applied to (e.g.,
emailis used on an email-type input, not on a checkbox). - The tokens are correctly ordered when multiple tokens are used (e.g., a section name followed by a hint token followed by the field name).
The rule flags fields where the autocomplete value is misspelled, uses a non-existent token, or is applied in an invalid way.
How to Fix It
- Identify all form fields that collect personal user information (name, email, address, phone number, etc.).
- Check if the data type matches one of the 53 input purposes defined in WCAG 2.1 Section 7.
- Add the correct
autocompletevalue to each matching field. Make sure:- The value is spelled correctly.
- It is appropriate for the input type.
- If using multiple tokens, they follow the correct order: optional section name (
section-*), optionalshippingorbilling, optionalhome,work,mobile,fax, orpager, and then the autofill field name.
- Set
autocomplete="off"only when you have a legitimate reason to disable autofill — and note that this does not exempt you from the rule if the field still collects identifiable user data.
Common autocomplete Values
Here are some of the most frequently used values:
| Purpose | autocomplete Value |
|---|---|
| Full name | name |
| Given (first) name | given-name |
| Family (last) name | family-name |
| Email address | email |
| Telephone number | tel |
| Street address | street-address |
| Postal code | postal-code |
| Country | country |
| Credit card number | cc-number |
| Username | username |
| New password | new-password |
| Current password | current-password |
Examples
Incorrect: Missing or Invalid autocomplete Values
<!-- Missing autocomplete attribute entirely -->
<label for="name">Full Name</label>
<input type="text" id="name" name="name">
<!-- Misspelled autocomplete value -->
<label for="email">Email</label>
<input type="email" id="email" name="email" autocomplete="emal">
<!-- Invalid autocomplete value -->
<label for="phone">Phone</label>
<input type="tel" id="phone" name="phone" autocomplete="phone-number">
In the examples above, the first field has no autocomplete attribute, the second has a typo (emal instead of email), and the third uses a non-standard value (phone-number instead of tel).
Correct: Valid autocomplete Values
<label for="name">Full Name</label>
<input type="text" id="name" name="name" autocomplete="name">
<label for="email">Email</label>
<input type="email" id="email" name="email" autocomplete="email">
<label for="phone">Phone</label>
<input type="tel" id="phone" name="phone" autocomplete="tel">
Correct: Using Multiple Tokens
When a form has separate shipping and billing sections, you can use additional tokens to distinguish them:
<fieldset>
<legend>Shipping Address</legend>
<label for="ship-street">Street Address</label>
<input type="text" id="ship-street" name="ship-street"
autocomplete="shipping street-address">
<label for="ship-zip">Postal Code</label>
<input type="text" id="ship-zip" name="ship-zip"
autocomplete="shipping postal-code">
</fieldset>
<fieldset>
<legend>Billing Address</legend>
<label for="bill-street">Street Address</label>
<input type="text" id="bill-street" name="bill-street"
autocomplete="billing street-address">
<label for="bill-zip">Postal Code</label>
<input type="text" id="bill-zip" name="bill-zip"
autocomplete="billing postal-code">
</fieldset>
Correct: Named Sections with section-*
You can use custom section names to group related fields when the same type of data appears multiple times:
<label for="home-tel">Home Phone</label>
<input type="tel" id="home-tel" name="home-tel"
autocomplete="section-home tel">
<label for="work-tel">Work Phone</label>
<input type="tel" id="work-tel" name="work-tel"
autocomplete="section-work tel">
By using valid, correctly applied autocomplete values, you ensure that assistive technologies can communicate the purpose of each field to users, browsers can offer reliable autofill, and your forms meet the requirements of WCAG 2.1 Success Criterion 1.3.5.
Some users need to adjust text spacing to make content readable. People with low vision may increase letter or word spacing to reduce visual crowding. People with cognitive disabilities, dyslexia, or attention deficit disorders often struggle to track lines of text that are tightly spaced — increasing line-height, letter-spacing, or word-spacing can make reading significantly easier.
When text-spacing properties are set inline with !important, they gain the highest specificity in the CSS cascade. This means user stylesheets, browser extensions, and assistive technology tools cannot override those values. The text becomes locked into a fixed spacing that may be difficult or impossible for some users to read.
This rule relates to WCAG 2.1 Success Criterion 1.4.12: Text Spacing (Level AA), which requires that no loss of content or functionality occurs when users adjust:
- Line height to at least 1.5 times the font size
- Spacing following paragraphs to at least 2 times the font size
- Letter spacing to at least 0.12 times the font size
- Word spacing to at least 0.16 times the font size
If inline !important declarations prevent these adjustments, the content fails this criterion.
How to Fix It
The fix is straightforward: do not use !important on inline style attributes for line-height, letter-spacing, or word-spacing. You have a few options:
- Remove
!importantfrom the inline style declaration. Without!important, users can override the value with a custom stylesheet. - Move styles to an external or embedded stylesheet. This is generally the best approach because it separates content from presentation and gives users more control through the cascade.
- If
!importantis truly necessary, apply it in a stylesheet rather than inline. Inline!importantstyles are virtually impossible for users to override, while stylesheet-level!importantcan still be overridden by user!importantrules.
Note that other inline style properties like font-size are not flagged by this rule — only the three text-spacing properties (line-height, letter-spacing, word-spacing) are checked.
Examples
Incorrect: Inline styles with !important
These examples fail because !important on inline text-spacing properties prevents user overrides.
<!-- line-height with !important — cannot be overridden -->
<p style="line-height: 1.5 !important;">
This text is locked to a specific line height.
</p>
<!-- letter-spacing with !important — cannot be overridden -->
<p style="letter-spacing: 2px !important;">
This text has fixed letter spacing.
</p>
<!-- word-spacing with !important — cannot be overridden -->
<p style="word-spacing: 4px !important;">
This text has fixed word spacing.
</p>
<!-- Mixed: word-spacing is fine, but letter-spacing has !important -->
<p style="word-spacing: 4px; letter-spacing: 2px !important; line-height: 1.8;">
Even one !important on a spacing property causes a failure.
</p>
Correct: Inline styles without !important
These examples pass because users can override the inline values with a custom stylesheet.
<!-- line-height without !important — overridable -->
<p style="line-height: 1.5;">
Users can adjust this line height with a custom stylesheet.
</p>
<!-- letter-spacing without !important — overridable -->
<p style="letter-spacing: 2px;">
Users can adjust this letter spacing.
</p>
<!-- word-spacing without !important — overridable -->
<p style="word-spacing: 4px;">
Users can adjust this word spacing.
</p>
<!-- Multiple spacing properties, all without !important -->
<p style="word-spacing: 4px; letter-spacing: 2px; line-height: 1.8;">
All three spacing properties can be overridden by the user.
</p>
<!-- font-size with !important is fine — not a text-spacing property -->
<p style="font-size: 200%;">
This does not trigger the rule.
</p>
Best practice: Use an external stylesheet instead
<!-- HTML -->
<p class="readable-text">
Styles are defined in the stylesheet, giving users full control.
</p>
/* CSS */
.readable-text {
line-height: 1.8;
letter-spacing: 0.05em;
word-spacing: 0.1em;
}
By keeping text-spacing styles in a stylesheet, you make it easy for users to apply their own overrides while maintaining your default design.
When a button lacks an accessible name, assistive technologies like screen readers can only announce it generically — for example, as "button" — with no indication of its purpose. This is a critical barrier for people who are blind or deafblind, as they rely entirely on programmatically determined names to understand and interact with interface controls. A sighted user might infer a button's purpose from an icon or visual context, but without a text-based name, that information is completely lost to assistive technology users.
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 is also covered by Section 508, EN 301 549 (9.4.1.2), and Trusted Tester guidelines, which require that the purpose of every link and button be determinable from its accessible name, description, or context.
How to fix it
Ensure every <button> element or element with role="button" has an accessible name through one of these methods:
- Visible text content inside the button element.
- A non-empty
aria-labelattribute that describes the button's purpose. - An
aria-labelledbyattribute that references an element containing visible, non-empty text. - A
titleattribute (use as a last resort, sincetitletooltips are inconsistently exposed across devices).
If a button is purely decorative and should be hidden from assistive technologies, you can assign role="presentation" or role="none" and remove it from the tab order with tabindex="-1". However, this is rare for interactive buttons.
Common mistakes to avoid
- Leaving a
<button>element completely empty. - Using only a
valueattribute on a<button>— unlike<input>elements, thevalueattribute on<button>does not provide an accessible name. - Setting
aria-labelto an empty string (aria-label=""). - Pointing
aria-labelledbyto an element that doesn't exist or contains no text. - Using only an icon or image inside a button without providing alternative text.
Examples
Incorrect: empty button
<button id="search"></button>
A screen reader announces this as "button" with no indication of its purpose.
Incorrect: button with only a value attribute
<button id="submit" value="Submit"></button>
The value attribute does not set the accessible name for <button> elements.
Incorrect: empty aria-label
<button id="close" aria-label=""></button>
An empty aria-label results in no accessible name.
Incorrect: aria-labelledby pointing to a missing or empty element
<button id="save" aria-labelledby="save-label"></button>
<div id="save-label"></div>
The referenced element exists but contains no text, so the button has no accessible name.
Correct: button with visible text
<button>Submit order</button>
Correct: icon button with aria-label
<button aria-label="Close dialog">
<svg aria-hidden="true" focusable="false">
<use href="#icon-close"></use>
</svg>
</button>
The aria-label provides the accessible name, while aria-hidden="true" on the SVG prevents duplicate announcements.
Correct: button labeled by another element
<h2 id="section-title">Shopping cart</h2>
<button aria-labelledby="section-title">
<svg aria-hidden="true" focusable="false">
<use href="#icon-arrow"></use>
</svg>
</button>
The button's accessible name is drawn from the referenced heading text.
Correct: button with aria-label and visible text
<button aria-label="Search products">Search</button>
When both aria-label and inner text are present, aria-label takes precedence as the accessible name. Use this when you need a more descriptive name than what the visible text alone conveys.
Correct: button with title (last resort)
<button title="Print this page">
<svg aria-hidden="true" focusable="false">
<use href="#icon-print"></use>
</svg>
</button>
The title attribute provides an accessible name, but visible text or aria-label are preferred because title tooltips may not be available to touch-screen or keyboard-only users.
Websites typically repeat navigation links, branding, and other interface elements across every page. While sighted mouse users can visually scan past these blocks and click wherever they want, keyboard-only users and screen reader users must navigate through every interactive element sequentially. Without a bypass mechanism, a keyboard user might need to press Tab dozens of times just to reach the primary content on each new page they visit. For users with severe motor impairments, this can take several minutes per page and cause fatigue or physical pain. Even users with less severe limitations will experience frustrating delays compared to mouse users, who can reach any link in a second or two.
Screen reader users also benefit significantly from bypass mechanisms. Landmarks like <main>, <nav>, and <header> allow screen readers to present a structural outline of the page, enabling users to jump directly to the section they need. Headings serve a similar purpose — screen reader users can navigate by heading level to quickly locate the main content area.
This rule maps to WCAG 2.4.1 Bypass Blocks (Level A), which requires that a mechanism is available to bypass blocks of content repeated on multiple pages. It is also required by Section 508 (specifically §1194.22(o)), the Trusted Tester guidelines, and EN 301 549. Because it is a Level A requirement, it represents the minimum baseline for accessibility compliance.
How the Rule Works
The axe bypass rule checks that a page includes at least one of the following:
- A landmark region (such as
<main>,<nav>,<header>, or<footer>) - A heading (an
<h1>through<h6>element) - An internal skip link (an anchor link that points to a location further down the page)
If none of these are present, the rule flags the page as a failure.
How to Fix It
The best approach is to use HTML5 landmark elements to structure your page. At a minimum, include a <main> element that wraps the primary content of the page. You should also use <header>, <nav>, and <footer> to identify other common sections. A page should have only one <main> landmark.
Additionally, consider adding a skip navigation link as the very first focusable element on the page. This provides an immediate shortcut for keyboard users who don't use screen readers and may not be able to navigate by landmarks.
Prefer native HTML5 elements over their ARIA equivalents. For example, use <main> rather than <div role="main">. Native elements are better supported and require less code.
Examples
Incorrect: No Landmarks, Headings, or Skip Links
This page has no structural landmarks, no headings, and no skip link. Keyboard users must tab through every element to reach the content.
<div class="header">
<div class="logo">My Site</div>
<div class="nav">
<a href="/home">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</div>
</div>
<div class="content">
<p>This is the main content of the page.</p>
</div>
<div class="footer">
<p>Footer information</p>
</div>
Correct: Using HTML5 Landmark Elements
Replacing generic <div> wrappers with semantic HTML5 elements gives the page proper structure that assistive technologies can use for navigation.
<header>
<div class="logo">My Site</div>
<nav>
<a href="/home">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
</header>
<main>
<h1>Welcome</h1>
<p>This is the main content of the page.</p>
<section>
<h2>Latest News</h2>
<p>Section content here.</p>
</section>
</main>
<footer>
<p>Footer information</p>
</footer>
Correct: Adding a Skip Navigation Link
A skip link gives keyboard users an immediate way to bypass repeated content. It is typically visually hidden until it receives focus.
<body>
<a class="skip-link" href="#main-content">Skip to main content</a>
<header>
<nav>
<a href="/home">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
</header>
<main id="main-content">
<h1>Page Title</h1>
<p>This is the main content of the page.</p>
</main>
<footer>
<p>Footer information</p>
</footer>
</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;
}
When the skip link receives keyboard focus, it becomes visible, and pressing Enter moves focus directly to the <main> element. Combined with proper landmark elements, this gives all users fast, reliable access to the page's primary content.
Screen readers announce definition lists in a specific way, conveying the relationship between terms (<dt>) and their descriptions (<dd>). When a <dl> element contains invalid direct children — such as <p>, <span>, or <li> elements — or when <dt> and <dd> elements appear in the wrong order, assistive technology cannot reliably parse the list. This primarily affects blind and deafblind users who depend on screen readers to understand content structure.
For example, a screen reader might announce a definition list by saying "definition list with 3 items," then reading each term followed by its definition. If the markup is malformed, the screen reader may skip items, miscount them, or fail to associate terms with their definitions.
This rule maps to WCAG Success Criterion 1.3.1: Info and Relationships (Level A), which requires that information, structure, and relationships conveyed visually are also available programmatically. A properly structured <dl> ensures the semantic relationship between terms and definitions is preserved in the accessibility tree.
How to Fix It
Follow these rules when building definition lists:
- Direct children of
<dl>must be limited to:<dt>,<dd>,<div>,<script>, or<template>elements. No other elements (like<p>,<span>,<li>, or plain text nodes) should appear as direct children. - Ordering matters: One or more
<dt>elements must come before one or more<dd>elements. A<dd>should never precede a<dt>within a group. - Using
<div>as a wrapper: You may wrap a<dt>/<dd>group in a<div>for styling purposes, but each<div>must contain a complete group (at least one<dt>followed by at least one<dd>). - No stray content: Don't place bare text or non-allowed elements directly inside the
<dl>.
Examples
Incorrect: Invalid direct child element
The <p> element is not a valid direct child of <dl>.
<dl>
<p>Beverage Types</p>
<dt>Coffee</dt>
<dd>A black hot drink made from roasted beans</dd>
</dl>
Incorrect: Wrong order of <dt> and <dd>
The <dd> element must follow the <dt>, not precede it.
<dl>
<dd>A black hot drink made from roasted beans</dd>
<dt>Coffee</dt>
</dl>
Incorrect: <dd> without a preceding <dt>
Every <dd> must be associated with at least one <dt>.
<dl>
<dd>An orphan definition with no term</dd>
</dl>
Correct: Basic definition list
<dl>
<dt>Coffee</dt>
<dd>A black hot drink made from roasted beans</dd>
<dt>Milk</dt>
<dd>A white cold drink</dd>
</dl>
Correct: Multiple definitions for a single term
<dl>
<dt>Coffee</dt>
<dd>A black hot drink made from roasted beans</dd>
<dd>A stimulating beverage containing caffeine</dd>
</dl>
Correct: Using <div> to wrap groups
Wrapping <dt>/<dd> groups in <div> elements is valid and useful for styling.
<dl>
<div>
<dt>Coffee</dt>
<dd>A black hot drink made from roasted beans</dd>
</div>
<div>
<dt>Milk</dt>
<dd>A white cold drink</dd>
</div>
</dl>
Correct: Multiple terms sharing one definition
<dl>
<dt>Latte</dt>
<dt>Café au lait</dt>
<dd>A coffee drink made with espresso and steamed milk</dd>
</dl>
Description lists follow a specific structural hierarchy in HTML. The <dl> element defines the list, and within it, <dt> elements represent terms while <dd> elements provide their corresponding descriptions. When <dt> or <dd> elements exist outside of a <dl>, the browser has no context to establish the relationship between terms and definitions. This makes the content semantically meaningless to assistive technologies.
Screen reader users are most affected by this issue. Screen readers announce description lists with specific cues — for example, telling users they've entered a list, how many items it contains, and the relationship between terms and descriptions. When <dt> and <dd> elements lack a <dl> parent, these announcements don't occur, and users who are blind or deafblind lose important structural context. Keyboard-only users and users with mobility impairments who rely on assistive technologies are also affected, as their tools may not properly navigate orphaned list items.
This rule relates to WCAG 2.0, 2.1, and 2.2 Success Criterion 1.3.1: Info and Relationships (Level A), which requires that information, structure, and relationships conveyed through presentation can be programmatically determined. A description list's structure conveys a meaningful relationship between terms and definitions, so the proper HTML hierarchy must be in place for that relationship to be communicated to all users.
How to fix it
- Wrap orphaned
<dt>and<dd>elements inside a<dl>parent element. - Ensure proper ordering —
<dt>elements should come before their associated<dd>elements. - Only place
<dt>and<dd>elements (or<div>elements that group<dt>/<dd>pairs) as direct children of<dl>. - Each
<dt>should have at least one corresponding<dd>, and vice versa, to form a complete term-description pair.
Examples
Incorrect: <dt> and <dd> without a <dl> parent
<dt>Coffee</dt>
<dd>A hot, caffeinated beverage</dd>
<dt>Milk</dt>
<dd>A cold, dairy-based drink</dd>
This is invalid because the <dt> and <dd> elements are not wrapped in a <dl>. Screen readers will not recognize these as a description list, and users will miss the term-definition relationships.
Correct: <dt> and <dd> wrapped in a <dl>
<dl>
<dt>Coffee</dt>
<dd>A hot, caffeinated beverage</dd>
<dt>Milk</dt>
<dd>A cold, dairy-based drink</dd>
</dl>
Correct: using <div> to group term-description pairs inside <dl>
HTML allows <div> elements as direct children of <dl> to group each <dt>/<dd> pair, which can be useful for styling:
<dl>
<div>
<dt>Coffee</dt>
<dd>A hot, caffeinated beverage</dd>
</div>
<div>
<dt>Milk</dt>
<dd>A cold, dairy-based drink</dd>
</div>
</dl>
Incorrect: <dd> nested inside an unrelated element
<div>
<dd>This description has no list context</dd>
</div>
A <dd> inside a <div> (or any non-<dl> parent) is invalid. Replace the <div> with a <dl> and add a corresponding <dt>:
<dl>
<dt>Term</dt>
<dd>This description now has proper list context</dd>
</dl>
The <title> element is the very first piece of information screen reader users hear when a page loads. It's also what appears in browser tabs, bookmarks, and search engine results. When a page has no title — or has an empty or generic one like "Untitled" — screen reader users are forced to read through the page content to figure out its purpose. For users who navigate between multiple open tabs or use their browser history to find a previous page, missing or vague titles create serious barriers.
This rule relates to WCAG 2.4.2 Page Titled (Level A), which requires that web pages have titles describing their topic or purpose. Because this is a Level A criterion, it represents a minimum baseline for accessibility. The rule also aligns with Trusted Tester guideline 12.A and EN 301 549.
The users most affected by missing or poor page titles include:
- Screen reader users (blind and deafblind users), who rely on the title as the first orientation cue when a page loads
- Users with cognitive disabilities, who benefit from clear, descriptive titles to understand where they are
- Keyboard-only users and anyone navigating between multiple tabs, who use titles to distinguish pages
How to fix it
- Add a
<title>element inside the<head>of every HTML document. - Write meaningful text inside the
<title>— it must not be empty or contain only whitespace. - Make each title unique across your site so users can distinguish between pages.
- Put the most unique information first. If you include a site or brand name, place it at the end (e.g., "Contact Us – Acme Corp" rather than "Acme Corp – Contact Us"). This way, screen reader users hear the distinguishing content immediately instead of listening to the same brand name on every page.
- Align the title with the page's
<h1>heading. They don't need to be identical, but they should be closely related since both describe the page's purpose. - Avoid placeholder text like "Untitled," "Page 1," or "New Document."
Beyond accessibility, descriptive titles improve SEO since search engines use them to filter, rank, and display results.
Examples
Incorrect: missing <title> element
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<h1>Our Products</h1>
</body>
</html>
This page has no <title> element at all. Screen reader users will hear no identifying information when the page loads.
Incorrect: empty <title> element
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<h1>Our Products</h1>
</body>
</html>
A <title> element is present but contains no text, which is equivalent to having no title.
Incorrect: generic or placeholder title
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Untitled Document</title>
</head>
<body>
<h1>Our Products</h1>
</body>
</html>
While technically not empty, a placeholder title provides no useful information about the page's content.
Correct: descriptive and unique title
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Our Products – Acme Corp</title>
</head>
<body>
<h1>Our Products</h1>
</body>
</html>
The title clearly describes the page content, places the unique information first, and includes the site name at the end for context. It also closely matches the <h1> heading on the page.
The id attribute serves as a unique identifier for an element within an HTML document. When id values are duplicated on active, focusable elements (elements that can receive keyboard focus, like inputs, buttons, links, and elements with tabindex), it creates a fundamental problem: the browser and assistive technologies have no reliable way to distinguish one element from another. This is different from duplicate IDs on non-focusable elements — while still invalid HTML, duplicate IDs on focusable elements have a more direct and serious impact on accessibility.
Why this matters
Screen readers rely on unique id values to build their internal model of the page. When a label references an id via the for attribute, or when aria-labelledby or aria-describedby points to an id, the assistive technology will typically only resolve the first element with that id. This means:
- Form labels may point to the wrong control. A
<label>usingforto reference a duplicatedidwill only be associated with the first matching element. The second form control becomes unlabeled for screen reader users. - ARIA relationships break. Attributes like
aria-labelledby,aria-describedby, andaria-controlsdepend on unique IDs to function correctly. - Table header associations fail. When
<td>elements use theheadersattribute to reference<th>elements byid, duplicated IDs cause incorrect or missing header announcements. - Client-side scripts malfunction. JavaScript methods like
document.getElementById()return only the first matching element, so event handlers and dynamic behavior may not apply to the intended element.
Users who are blind or deafblind are most seriously affected, as they depend entirely on assistive technology to navigate and interact with focusable elements. The user impact of this issue is considered serious.
While WCAG 2.0's Success Criterion 4.1.1 (Parsing) originally required valid markup including unique IDs, this criterion was deprecated in WCAG 2.2 because modern browsers handle parsing errors more consistently. However, duplicate active IDs still cause real accessibility failures — particularly violations of SC 1.3.1 (Info and Relationships) and SC 4.1.2 (Name, Role, Value) — because they break the programmatic associations that assistive technology depends on.
How to fix it
- Identify all focusable elements with duplicate
idvalues. You can use the axe accessibility checker, browser developer tools, or the W3C HTML Validator to find duplicates. - Assign a unique
idto each focusable element. Append a distinguishing suffix, use a naming convention, or generate unique identifiers. - Update all references to the renamed IDs, including
<label for="">,aria-labelledby,aria-describedby,aria-controls,headers, and any JavaScript that targets the element byid.
Examples
Incorrect: duplicate id on focusable elements
In this example, two input fields share the same id of "email". The label only associates with the first input, leaving the second input unlabeled for screen reader users.
<label for="email">Personal Email</label>
<input type="email" id="email" name="personal_email">
<label for="email">Work Email</label>
<input type="email" id="email" name="work_email">
Correct: unique id on each focusable element
Each input has a distinct id, and each label correctly references its corresponding control.
<label for="personal-email">Personal Email</label>
<input type="email" id="personal-email" name="personal_email">
<label for="work-email">Work Email</label>
<input type="email" id="work-email" name="work_email">
Incorrect: duplicate id breaking ARIA relationships
Here, two buttons share the same id, so aria-describedby on the dialog can only resolve to the first button — the description association for the second context is lost.
<button id="save-btn" aria-describedby="save-help">Save Draft</button>
<p id="save-help">Saves without publishing.</p>
<button id="save-btn" aria-describedby="publish-help">Save & Publish</button>
<p id="publish-help">Saves and makes content live.</p>
Correct: unique id values with proper ARIA references
<button id="save-draft-btn" aria-describedby="save-help">Save Draft</button>
<p id="save-help">Saves without publishing.</p>
<button id="save-publish-btn" aria-describedby="publish-help">Save & Publish</button>
<p id="publish-help">Saves and makes content live.</p>
Why This Is an Accessibility Problem
In HTML, the id attribute is designed to be a unique identifier for a single element in the document. When two or more elements share the same id, the browser has no reliable way to determine which element is being referenced. This becomes a critical accessibility barrier when that id is used to create relationships between elements — such as linking a <label> to a form input, or connecting a description to a widget via aria-describedby.
Assistive technologies like screen readers rely on these id-based relationships to communicate information to users. When duplicates exist, the screen reader will typically resolve the reference to the first element in the DOM with that id, which may not be the intended target. This means:
- A blind or deafblind user may hear the wrong label for a form field, or no label at all.
- An ARIA relationship like
aria-labelledbyoraria-describedbymay point to the wrong content, giving users incorrect or missing context. - Interactive components that depend on
aria-owns,aria-controls, oraria-activedescendantmay break entirely.
This rule relates to WCAG Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that all user interface components have accessible names and roles that can be programmatically determined. Duplicate id values used in ARIA or label associations directly undermine this requirement by creating ambiguous or broken programmatic relationships.
How to Fix It
- Identify all duplicate
idvalues that are referenced by ARIA attributes (aria-labelledby,aria-describedby,aria-controls,aria-owns,aria-activedescendant, etc.) or by a<label>element'sforattribute. - Rename the duplicate
idvalues so that each one is unique within the document. - Update any references to those
idvalues in ARIA attributes orforattributes to match the new unique values. - Verify that each relationship still works correctly by testing with a screen reader or the axe accessibility checker.
Examples
Incorrect: Duplicate id on elements referenced by for
In this example, two inputs share the same id of "email". The second <label> intends to reference the second input, but both for attributes resolve to the first input.
<label for="email">Personal Email</label>
<input type="email" id="email">
<label for="email">Work Email</label>
<input type="email" id="email">
A screen reader user tabbing to the second input would hear no label or the wrong label, making it impossible to know what information to enter.
Correct: Unique id values for each input
<label for="personal-email">Personal Email</label>
<input type="email" id="personal-email">
<label for="work-email">Work Email</label>
<input type="email" id="work-email">
Incorrect: Duplicate id referenced by aria-labelledby
<span id="section-title">Shipping Address</span>
<div role="group" aria-labelledby="section-title">
<!-- shipping fields -->
</div>
<span id="section-title">Billing Address</span>
<div role="group" aria-labelledby="section-title">
<!-- billing fields -->
</div>
Both groups would be announced as "Shipping Address" because the browser resolves both aria-labelledby references to the first <span> with id="section-title".
Correct: Unique id values for each referenced element
<span id="shipping-title">Shipping Address</span>
<div role="group" aria-labelledby="shipping-title">
<!-- shipping fields -->
</div>
<span id="billing-title">Billing Address</span>
<div role="group" aria-labelledby="billing-title">
<!-- billing fields -->
</div>
Incorrect: Duplicate id used in aria-describedby
<p id="hint">Must be at least 8 characters.</p>
<label for="password">Password</label>
<input type="password" id="password" aria-describedby="hint">
<p id="hint">Re-enter your password to confirm.</p>
<label for="confirm-password">Confirm Password</label>
<input type="password" id="confirm-password" aria-describedby="hint">
Correct: Unique id values for each description
<p id="password-hint">Must be at least 8 characters.</p>
<label for="password">Password</label>
<input type="password" id="password" aria-describedby="password-hint">
<p id="confirm-hint">Re-enter your password to confirm.</p>
<label for="confirm-password">Confirm Password</label>
<input type="password" id="confirm-password" aria-describedby="confirm-hint">
The id attribute is the primary mechanism for uniquely identifying an element in the DOM. Many accessibility features depend on id references to create relationships between elements — for example, a <label> element uses its for attribute to point to the id of a form input, and aria-labelledby or aria-describedby attributes reference one or more id values to associate descriptive text with a control.
When two or more elements share the same id, browsers and assistive technologies have no reliable way to determine which element is being referenced. In practice, most screen readers and client-side scripts will act on only the first element with a given id and silently ignore subsequent ones. This means:
- Screen reader users may hear incorrect or missing labels for form fields, table cells, or other interactive elements.
- Keyboard-only users who rely on skip links or in-page anchors may be taken to the wrong location on the page.
- Users of voice control software may be unable to target the correct element by its label.
While duplicate id values are technically an HTML validation error (previously addressed by WCAG 1.0 and the now-deprecated WCAG 2.0 Success Criterion 4.1.1 — Parsing), they remain a practical accessibility concern. Valid markup eliminates an entire category of potential accessibility failures, and ensuring unique id values is one of the simplest ways to maintain it.
How to Fix the Problem
- Audit your page for duplicate
idvalues. You can use the W3C Markup Validator, browser DevTools, or an accessibility testing tool like axe to find them quickly. - Rename any duplicated
idvalues so that each one is unique within the document. Choose descriptive, meaningful names that reflect the element's purpose. - Update all references to the renamed
id. Search for anyfor,aria-labelledby,aria-describedby,aria-controls,headers, or anchorhrefattributes that pointed to the oldidand update them to match the new value. - Check dynamically generated content. If your page injects HTML via JavaScript or server-side templates (e.g., rendering a component multiple times in a loop), make sure each instance generates a unique
id, such as by appending an index or a unique identifier.
Examples
Incorrect: Duplicate id Values
In this example, two input elements share the same id of "email". The second label's for attribute points to "email", but the browser associates it with the first input, leaving the second input effectively unlabeled for assistive technology users.
<label for="email">Personal Email</label>
<input type="email" id="email" name="personal_email">
<label for="email">Work Email</label>
<input type="email" id="email" name="work_email">
Correct: Unique id Values
Each input has a distinct id, and the corresponding label elements reference the correct one.
<label for="personal-email">Personal Email</label>
<input type="email" id="personal-email" name="personal_email">
<label for="work-email">Work Email</label>
<input type="email" id="work-email" name="work_email">
Incorrect: Duplicate id in aria-labelledby References
Here, two sections use the same id for their headings. An aria-labelledby reference on the second region will resolve to the first heading instead.
<section aria-labelledby="section-title">
<h2 id="section-title">Latest News</h2>
<p>News content here.</p>
</section>
<section aria-labelledby="section-title">
<h2 id="section-title">Upcoming Events</h2>
<p>Events content here.</p>
</section>
Correct: Unique id Values for aria-labelledby
<section aria-labelledby="news-title">
<h2 id="news-title">Latest News</h2>
<p>News content here.</p>
</section>
<section aria-labelledby="events-title">
<h2 id="events-title">Upcoming Events</h2>
<p>Events content here.</p>
</section>
Incorrect: Duplicate id from Repeated Components
A common source of duplicate id values is rendering the same component template multiple times.
<div class="card">
<button aria-describedby="card-desc">Buy Now</button>
<p id="card-desc">Free shipping on this item.</p>
</div>
<div class="card">
<button aria-describedby="card-desc">Buy Now</button>
<p id="card-desc">Ships within 2 days.</p>
</div>
Correct: Unique id for Each Component Instance
Append a unique suffix (such as a product ID or index) to each id.
<div class="card">
<button aria-describedby="card-desc-1">Buy Now</button>
<p id="card-desc-1">Free shipping on this item.</p>
</div>
<div class="card">
<button aria-describedby="card-desc-2">Buy Now</button>
<p id="card-desc-2">Ships within 2 days.</p>
</div>
Screen reader users frequently navigate web pages by jumping between headings to get an overview of the content, much like sighted users visually scan a page. When a heading element is empty or its content is hidden from assistive technologies, the screen reader announces something like "heading level 2" with nothing after it. This is disorienting and can make users think content is missing or that the page is broken.
This rule is flagged as a Deque best practice and primarily affects users who are blind or deafblind and rely on screen readers, though it also impacts users with mobility impairments who use heading-based navigation. Well-structured, meaningful headings are foundational to an accessible page — they communicate the document's outline and help all users find the information they need quickly.
A heading can be effectively "empty" in several ways:
- The element contains no text at all (
<h2></h2>) - The element contains only whitespace or non-text elements with no accessible name
- The text is hidden from assistive technologies using
aria-hidden="true"or CSS likedisplay: none
How to fix it
- Add meaningful text to every heading element. Headings should be brief, clear, and descriptive of the section they introduce.
- Remove heading tags from elements that aren't actually headings. Don't use
<h1>through<h6>just for visual styling — use CSS instead. - Don't hide heading text from screen readers using
aria-hidden="true"ordisplay: none. If a heading shouldn't be visible on screen but is needed for accessibility, use a visually-hidden CSS technique instead. - Maintain a logical heading hierarchy. Headings should be ordered by level (
<h1>, then<h2>, then<h3>, etc.) to accurately convey the structure of the page.
As a quick check, read through only the headings on your page. If they don't give you a clear sense of the page's content and structure, rewrite them.
Examples
Empty heading (incorrect)
<h2></h2>
<p>This section has no heading text.</p>
The <h2> is announced by a screen reader, but there's no content to read.
Heading with only whitespace (incorrect)
<h3> </h3>
Whitespace alone doesn't provide an accessible name, so this is treated as empty.
Heading hidden from assistive technologies (incorrect)
<h2 aria-hidden="true">About Our Team</h2>
The aria-hidden="true" attribute hides the heading from screen readers entirely, even though sighted users can see it. This creates a gap in the page structure for assistive technology users.
Heading hidden with CSS (incorrect)
<h2 style="display: none;">Contact Us</h2>
Using display: none removes the heading from both the visual layout and the accessibility tree, making it inaccessible to everyone.
Heading with visible text (correct)
<h2>About Our Team</h2>
<p>We are a small company dedicated to accessible design.</p>
The heading clearly describes the section that follows.
Visually hidden heading for screen readers only (correct)
When a heading is needed for document structure but shouldn't appear visually, use a visually-hidden class rather than display: none or aria-hidden:
<style>
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style>
<h2 class="visually-hidden">Main Navigation</h2>
<nav>
<a href="/home">Home</a>
<a href="/about">About</a>
</nav>
This keeps the heading accessible to screen readers while hiding it visually.
Heading with an image that has alt text (correct)
<h1>
<img src="logo.png" alt="Acme Corporation">
</h1>
The heading's accessible name comes from the image's alt attribute, so the heading is not considered empty.
Table headers play a critical role in making data tables understandable. They label rows and columns so users can interpret the data within each cell. When a table header is empty — containing no visible text — it creates confusion for everyone. Sighted users see a blank cell where a label should be, and screen reader users hear nothing meaningful when navigating to that header, making it difficult or impossible to understand the relationship between the header and its associated data cells.
Screen readers rely on table headers to announce context as users navigate through cells. For example, when a screen reader user moves between cells in a column, the column header is announced to remind them which column they're in. If that header is empty, the user loses that context entirely.
This rule is identified as empty-table-header in axe-core and is classified as a Deque Best Practice. It primarily affects users who are blind or have low vision and rely on screen readers, but it also impacts sighted users who depend on clear visual labels to understand table data.
Why visible text matters
It's important to note that this rule specifically checks for visible text. Using only aria-label or aria-labelledby on an empty <th> does not satisfy this rule. While those attributes may provide a name for assistive technology, they don't help sighted users who also need to see the header text. The goal is to ensure that the header's purpose is communicated visually and programmatically.
How to fix it
- Add visible text to every
<th>element so it clearly describes the row or column it represents. - If the cell isn't a header, change it from a
<th>to a<td>. This is common for empty corner cells in tables where row and column headers intersect. - Avoid using only ARIA attributes like
aria-labelon an otherwise empty header. Always include visible text content.
Examples
Incorrect: empty table header
The <th> element has no text content, leaving both sighted and screen reader users without context.
<table>
<tr>
<th></th>
<th>Q1</th>
<th>Q2</th>
</tr>
<tr>
<th>Revenue</th>
<td>$100k</td>
<td>$150k</td>
</tr>
</table>
Incorrect: table header with only aria-label
While aria-label provides a name for assistive technology, there is no visible text for sighted users.
<table>
<tr>
<th aria-label="Student Name"></th>
<th aria-label="Grade"></th>
</tr>
<tr>
<td>Alice</td>
<td>A</td>
</tr>
</table>
Correct: table headers with visible text
Each <th> contains visible text that clearly describes its column.
<table>
<tr>
<th>Student Name</th>
<th>Grade</th>
</tr>
<tr>
<td>Alice</td>
<td>A</td>
</tr>
</table>
Correct: using <td> for a non-header cell
When the top-left corner cell of a table isn't functioning as a header, use <td> instead of an empty <th>.
<table>
<tr>
<td></td>
<th>Q1</th>
<th>Q2</th>
</tr>
<tr>
<th>Revenue</th>
<td>$100k</td>
<td>$150k</td>
</tr>
</table>
Correct: visually hidden text for special cases
In rare cases where a header needs visible text for assistive technology but the visual design calls for no visible label, you can use a CSS visually-hidden technique. Note that this is a compromise — visible text is always preferred.
<table>
<tr>
<th>
<span class="visually-hidden">Category</span>
</th>
<th>Q1</th>
<th>Q2</th>
</tr>
<tr>
<th>Revenue</th>
<td>$100k</td>
<td>$150k</td>
</tr>
</table>
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
When users navigate a web page using a keyboard or screen reader, they move through what's called the "focus order" — the sequence of interactive elements that receive focus. Each time an element receives focus, a screen reader announces its role (e.g., "button," "link," "checkbox") along with its name and state. This role is how users understand what type of control they've landed on and what actions they can take.
If a focusable element has no role — for example, a <div> made focusable with tabindex="0" — the screen reader may read the element's text content but will provide no context about what the element is. The user hears text but has no idea whether to press Enter, type input, or toggle a state. Similarly, if an element has an inappropriate role like presentation or an abstract role like widget, assistive technologies cannot convey meaningful interaction patterns.
This issue primarily affects blind and deafblind users who rely on screen readers, and users with motor disabilities who navigate exclusively with a keyboard. Without proper role information, these users cannot efficiently or accurately interact with page controls.
Why Roles Matter
This rule aligns with accessibility best practices recommended by Deque and RGAA. While it doesn't map to a single WCAG success criterion, it supports several principles:
- WCAG 4.1.2 (Name, Role, Value): All user interface components must expose their role, name, and state to assistive technologies.
- WCAG 2.1.1 (Keyboard): All functionality must be operable through a keyboard. Meaningful role information is essential for keyboard users to understand what they can do with a focused element.
When an element in the focus order has a valid, appropriate role, screen readers can:
- Announce the type of control (e.g., "button," "textbox," "menu item").
- Inform users of available interactions (e.g., "press Enter to activate," "use arrow keys to navigate").
- Communicate state changes (e.g., "checked," "expanded").
How to Fix the Problem
Use Native HTML Elements First
The simplest and most reliable fix is to use the correct native HTML element. Native elements like <button>, <a>, <input>, and <select> come with built-in roles, keyboard behavior, and accessibility support — no extra attributes needed.
Add ARIA Roles to Custom Widgets
If you must use a non-semantic element (like <div> or <span>) as an interactive control, you need to:
- Add an appropriate
roleattribute (e.g.,role="button",role="checkbox",role="tab"). - Ensure all required keyboard interactions are implemented.
- Manage ARIA states and properties (e.g.,
aria-checked,aria-expanded).
Avoid Abstract Roles
ARIA defines abstract roles like command, input, landmark, range, section, sectionhead, select, structure, and widget. These exist only as part of the role taxonomy and must never be used directly on elements. Always use a concrete role instead.
Remove tabindex from Non-Interactive Elements When Possible
If an element doesn't need to be interactive, consider removing its tabindex attribute entirely so it doesn't appear in the focus order.
Appropriate Roles for Interactive Content
Here are some common categories of valid roles for focusable elements:
- Widget roles:
button,checkbox,combobox,link,listbox,menu,menuitem,menuitemcheckbox,menuitemradio,option,radio,scrollbar,slider,spinbutton,switch,tab,textbox,treeitem - Composite widget roles:
grid,menubar,radiogroup,tablist,toolbar,tree,treegrid - Landmark roles:
banner,complementary,contentinfo,main,navigation,region,search - Document structure roles:
dialog,alertdialog,application,document,log,status,timer,tooltip
Examples
Incorrect: Focusable Element With No Role
This <div> can receive focus, but a screen reader won't announce what it is:
<div tabindex="0" onclick="submitForm()">
Submit
</div>
A screen reader user will hear "Submit" but won't know it's meant to be a button.
Correct: Using a Native HTML Button
<button type="button" onclick="submitForm()">
Submit
</button>
The <button> element has a built-in button role. The screen reader announces "Submit, button."
Correct: Adding an ARIA Role to a Custom Widget
If you cannot use a native <button>, add the appropriate role and keyboard support:
<div role="button" tabindex="0" onclick="submitForm()" onkeydown="handleKeydown(event)">
Submit
</div>
Now the screen reader announces "Submit, button." You must also implement onkeydown to handle Enter and Space key presses, since a <div> doesn't natively support those interactions.
Incorrect: Using an Abstract Role
<div role="command" tabindex="0">
Save
</div>
The command role is abstract and must not be used on elements. Assistive technologies will not recognize it as a valid role.
Correct: Replacing the Abstract Role
<div role="button" tabindex="0" onkeydown="handleKeydown(event)">
Save
</div>
Incorrect: Non-Interactive Role on a Focusable Element
<span role="paragraph" tabindex="0" onclick="openMenu()">
Options
</span>
The paragraph role is not interactive. The element will not behave as expected for assistive technology users, and may not even receive focus in some screen readers.
Correct: Using an Appropriate Widget Role
<span role="button" tabindex="0" onclick="openMenu()" onkeydown="handleKeydown(event)">
Options
</span>
Incorrect: Focusable Custom Checkbox Without a Role
<div tabindex="0" class="custom-checkbox" onclick="toggleCheck(this)">
Accept terms
</div>
Correct: Custom Checkbox With Proper Role and State
<div role="checkbox" tabindex="0" aria-checked="false" onclick="toggleCheck(this)" onkeydown="handleKeydown(event)">
Accept terms
</div>
The role="checkbox" tells the screen reader this is a checkbox, and aria-checked communicates its current state. Remember to update aria-checked in your JavaScript when the state changes.
When a form field has more than one <label> element pointing to it (either via the for attribute or by nesting), assistive technologies have no reliable way to determine which label is the correct one. This inconsistency means that users who are blind, have low vision, or are deafblind may hear the wrong label, an incomplete label, or a confusing combination of labels when interacting with a form. Users with mobility impairments also benefit from properly associated labels, since a single clear <label> expands the clickable area of the associated input.
This rule relates to WCAG 2.0, 2.1, and 2.2 Success Criterion 3.3.2: Labels or Instructions (Level A), which requires that labels or instructions are provided when content requires user input. Multiple conflicting labels undermine this requirement because the user cannot reliably receive a single, clear label for the field.
How to Fix
Ensure that each form field has only one <label> element associated with it. You can associate a label with a field in one of two ways — but use only one label per field:
- Explicit association — Use the
forattribute on the<label>matching theidof the input. - Implicit association — Wrap the input inside the
<label>element.
If you need to provide additional descriptive text beyond the label, use aria-describedby to point to supplementary instructions rather than adding a second <label>.
If you have a situation where one label must be visually hidden, hide the redundant label using CSS (display: none or visibility: hidden) so it is fully removed from the accessibility tree, and remove its for attribute. Using aria-hidden="true" alone on a <label> is not sufficient to prevent all screen readers from associating it with the field.
Examples
Incorrect: Two explicit labels for one input
Both <label> elements use for="username", causing unpredictable screen reader behavior.
<label for="username">Username</label>
<label for="username">Enter your username</label>
<input type="text" id="username" />
Incorrect: One explicit and one implicit label
The input is both wrapped in a <label> and referenced by another <label> via for.
<label for="email">Email</label>
<label>
Email address:
<input type="text" id="email" />
</label>
Incorrect: Nested labels
Labels should never be nested inside each other.
<label>
Enter your comments:
<label>
Comments:
<textarea id="comments"></textarea>
</label>
</label>
Correct: Single explicit label
One <label> with a for attribute matching the input's id.
<label for="username">Username</label>
<input type="text" id="username" />
Correct: Single implicit label
The input is wrapped inside a single <label>.
<label>
Email address:
<input type="text" id="email" />
</label>
Correct: Label with supplementary instructions using aria-describedby
When you need to provide extra guidance beyond the label, use aria-describedby instead of a second label.
<label for="password">Password</label>
<input type="password" id="password" aria-describedby="password-hint" />
<p id="password-hint">Must be at least 8 characters with one number.</p>
Correct: Using the title attribute as a label
When a visible label is not appropriate (rare cases), the title attribute can serve as an accessible name.
<textarea id="search" title="Search terms"></textarea>
Correct: Select inside a single label
<label>
Choose an option:
<select id="options">
<option selected>Option A</option>
<option>Option B</option>
</select>
</label>
When a <frame> or <iframe> has tabindex="-1", the browser removes it from the sequential keyboard navigation order. This means that any focusable elements inside the frame — such as links, buttons, form controls, or other interactive elements — become completely unreachable via the keyboard. If the frame also has scrollable content, keyboard users cannot scroll it either, since focus can never enter the frame to begin with.
This creates a serious barrier for people who rely on keyboards to navigate, including blind users who use screen readers and people with mobility disabilities who cannot use a mouse. Content trapped inside an inaccessible frame is effectively hidden from these users, even though it may be fully visible on screen.
Related WCAG Success Criteria
This rule maps to WCAG 2.1 Success Criterion 2.1.1: Keyboard (Level A), which requires that all functionality be operable through a keyboard interface without requiring specific timings for individual keystrokes. When focusable content is locked inside a frame with tabindex="-1", this criterion is violated because keyboard users cannot access or interact with that content.
This is a Level A requirement — the most fundamental level of accessibility — meaning it must be met for a page to be considered minimally accessible.
How to Fix It
- Remove
tabindex="-1"from any<frame>or<iframe>that contains focusable content. Without an explicittabindex, the browser will handle focus naturally and allow keyboard users to tab into the frame. - Use
tabindex="0"if you need to explicitly include the frame in the tab order. - Only use
tabindex="-1"on frames that genuinely contain no focusable or interactive content. Even then, be cautious — if the frame's content changes later to include interactive elements, the negative tabindex will silently create an accessibility barrier.
As a general best practice, avoid using tabindex="-1" on frames entirely. It's easy for frame content to change over time, and a negative tabindex can turn into an accidental accessibility issue after a routine content update.
Examples
Incorrect: Frame with focusable content and tabindex="-1"
The button inside this iframe is unreachable by keyboard because tabindex="-1" prevents focus from entering the frame.
<iframe
srcdoc="<button>Click me</button>"
tabindex="-1"
title="Interactive widget">
</iframe>
Correct: Frame with focusable content and no negative tabindex
Removing tabindex="-1" allows keyboard users to tab into the frame and reach the button.
<iframe
srcdoc="<button>Click me</button>"
title="Interactive widget">
</iframe>
Correct: Frame with focusable content and tabindex="0"
Using tabindex="0" explicitly places the frame in the natural tab order.
<iframe
srcdoc="<button>Click me</button>"
tabindex="0"
title="Interactive widget">
</iframe>
Correct: Frame with no focusable content and tabindex="-1"
When a frame contains only static, non-interactive content (no links, buttons, or form controls), using tabindex="-1" is acceptable because there is nothing inside that requires keyboard access.
<iframe
srcdoc="<p>Hello world</p>"
tabindex="-1"
title="Static content display">
</iframe>
Why This Matters
Web pages often embed content using iframe or frame elements — for ads, third-party widgets, embedded forms, video players, or even internal application components. Each frame is essentially its own document with its own DOM. If axe-core is not running inside those frames, any accessibility violations within them go completely undetected.
This is classified as a critical user impact issue — not because the frame itself is inaccessible, but because undetected violations inside frames can affect all users with disabilities. Blind users relying on screen readers, sighted keyboard-only users, and deafblind users could all encounter barriers hidden within untested frame content. Missing form labels, broken focus management, insufficient color contrast, or missing alternative text inside frames would remain invisible to your testing process.
This rule is a Deque best practice rather than a specific WCAG success criterion. However, the violations that go undetected inside untested frames can relate to numerous WCAG criteria, including 1.1.1 (Non-text Content), 1.3.1 (Info and Relationships), 2.1.1 (Keyboard), 2.4.3 (Focus Order), 4.1.2 (Name, Role, Value), and many others. Comprehensive accessibility testing requires that all content on a page is analyzed, including content within frames.
How the Rule Works
When the iframes configuration property is set to true, axe-core attempts to run inside every iframe and frame element on the page. The rule uses both frame and iframe selectors to check whether the axe-core script is present in each frame's document. If axe-core is not found inside a frame, the rule returns a "needs review" result for that element.
The rule operates at the page level — meaning results from nodes across different frames are combined into a single result when the entire page is tested. An optional after function processes results from all frames together.
How to Fix It
There are several approaches to ensure frames are properly tested:
Use axe-core's built-in iframe support. When running axe-core programmatically, set the
iframesoption totrue(this is the default). axe-core will automatically attempt to communicate with frames to gather results — but the axe-core script must be present in each frame for this to work.Inject axe-core into all frames. If you control the frame content, include the axe-core script in those documents. If you're using a testing framework like Selenium, Puppeteer, or Playwright, inject the axe-core script into each frame before running the analysis.
Use axe DevTools browser extension. The axe DevTools extension handles frame injection automatically in most cases, making it the simplest option for manual testing.
For third-party frames you don't control, acknowledge that testing coverage is limited and consider testing the third-party content separately, or document it as an area requiring manual review.
Examples
Frame that triggers the issue
An iframe is present on the page, but axe-core is not loaded inside it. The content within the frame remains untested:
<main>
<h1>Our Application</h1>
<iframe src="https://example.com/embedded-form" title="Contact form"></iframe>
</main>
If https://example.com/embedded-form does not have axe-core loaded, axe will flag this frame as needing review.
Correct setup with axe-core injected
When using a testing tool like Playwright, ensure axe-core is injected into all frames:
const { AxeBuilder } = require('@axe-core/playwright');
// AxeBuilder automatically analyzes iframe content
const results = await new AxeBuilder({ page })
.analyze();
Frame content that includes axe-core
If you control the frame source, include the axe-core script directly:
<!-- Content inside the iframe document -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Embedded Form</title>
<script src="/vendor/axe-core/axe.min.js"></script>
</head>
<body>
<form>
<label for="email">Email address</label>
<input type="email" id="email" name="email">
<button type="submit">Subscribe</button>
</form>
</body>
</html>
Properly structured frame on the parent page
Always ensure your frames have descriptive title attributes so users understand their purpose:
<iframe src="/embedded-form.html" title="Newsletter subscription form"></iframe>
This won't fix the axe-core injection issue by itself, but it ensures the frame is accessible while you work on getting full test coverage inside it.
Screen reader users rely on frame titles to understand what each embedded region of a page contains. Many screen readers offer a feature that lists all frames on a page by their titles, allowing users to jump directly to the one they need. When frames lack titles or share identical titles, this feature becomes useless — users are left guessing which frame is which, or the screen reader falls back to unhelpful information like "frame," "javascript," a filename, or a URL.
This issue primarily affects users who are blind, deafblind, or who navigate with assistive technologies. It relates to WCAG 2.0, 2.1, and 2.2 Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that all user interface components have an accessible name that can be programmatically determined. When frames share a title, their accessible names fail to uniquely identify them, violating this criterion. The rule is also covered by Trusted Tester guideline 12.D, which requires that the combination of accessible name and description for each <iframe> describes its content, as well as EN 301 549 section 9.4.1.2.
How to Fix It
- Add a
titleattribute to every<frame>and<iframe>element. - Make each title unique across the page. No two frames should share the same title.
- Make each title descriptive. The title should clearly summarize the content or purpose of the frame. Avoid generic labels like "frame" or "untitled."
- Match the frame's inner document
<title>to thetitleattribute on the frame element when possible. Some screen readers replace the frame'stitleattribute with the inner document's<title>element, so keeping them consistent ensures a reliable experience.
Tips for Writing Good Frame Titles
- Keep titles brief but informative — describe what the frame contains, not just that it exists.
- Put the most distinctive information first. If you include a brand name, place it at the end so users don't have to hear it repeatedly while scanning a list of frames.
- Replace placeholder titles like "untitled" or "page" with meaningful descriptions.
- If the framed content has a visible heading, consider aligning the frame title with that heading for consistency.
Examples
Incorrect: Frames with Duplicate Titles
In this example, two iframes share the same title, making it impossible for screen reader users to tell them apart.
<iframe src="/news.html" title="Company Updates"></iframe>
<iframe src="/events.html" title="Company Updates"></iframe>
Incorrect: Frame with an Empty Title
An empty title provides no useful information to assistive technology users.
<iframe src="/contact.html" title=""></iframe>
Incorrect: Frames with No Title
Without any title attribute, screen readers fall back to announcing unhelpful information like the URL or "frame."
<iframe src="/navigation.html"></iframe>
<iframe src="/main-content.html"></iframe>
Correct: Frames with Unique, Descriptive Titles
Each frame has a distinct title that clearly describes its content.
<iframe src="/news.html" title="Latest Company News"></iframe>
<iframe src="/events.html" title="Upcoming Events Calendar"></iframe>
Correct: Frame Title Matching the Inner Document Title
For the best experience, align the title attribute with the <title> element in the framed document.
<!-- Parent page -->
<iframe src="/contact.html" title="Contact Form"></iframe>
<!-- contact.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>Contact Form</title>
</head>
<body>
<h1>Contact Us</h1>
<form>
<label for="email">Email</label>
<input type="email" id="email" name="email">
<button type="submit">Send</button>
</form>
</body>
</html>
Correct: Hiding Decorative or Non-Content Frames
If a frame is purely decorative or contains no meaningful content for users (such as a tracking pixel), hide it from assistive technology entirely instead of giving it a misleading title.
<iframe src="/tracking-pixel.html" title="" aria-hidden="true" tabindex="-1"></iframe>
Note that this approach should only be used when the frame genuinely contains no content that any user would need to access.
Screen reader users can pull up a list of all frames on a page and navigate between them using their titles. When frames lack accessible names, users hear generic labels like "frame," "JavaScript," or a raw URL — none of which convey the purpose or content of the frame. This forces users to enter each frame just to figure out what it contains, which is time-consuming and frustrating.
This issue primarily affects users who are blind or deafblind and rely on assistive technology, but it also impacts users with mobility disabilities who navigate by frame lists or landmarks.
Relevant WCAG Success Criteria
This rule relates to WCAG 2.2 Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that all user interface components have a name that can be programmatically determined. Frames are interactive containers that users navigate into and out of, so they must have an accessible name.
It is also explicitly required by Section 508 (§1194.22(i)), which states that frames shall be titled with text that facilitates frame identification and navigation.
How to Fix It
Give every <iframe> and <frame> element a clear, descriptive accessible name using one of these methods:
titleattribute — The most common approach. Add atitlethat briefly describes the frame's content.aria-labelattribute — Provides an accessible name directly on the element.aria-labelledbyattribute — References another element's text as the accessible name.
Tips for Writing Good Frame Titles
- Be brief and descriptive. A title like "Product search results" is far more useful than "frame1" or "content."
- Make each title unique. If you have multiple frames, each one should have a distinct title so users can tell them apart.
- Match the frame's internal document title. Some screen readers replace the frame's
titleattribute with the<title>element inside the framed document. For consistency, keep them the same or very similar. - Avoid placeholder text. Titles like "untitled" or "iframe" provide no useful information.
- Put unique information first. If you include a brand name, place it after the descriptive content so users hear the most useful information immediately.
Examples
Incorrect: <iframe> Without an Accessible Name
The frame has no title, aria-label, or aria-labelledby, so screen readers cannot describe it.
<iframe src="https://example.com/video-player"></iframe>
Incorrect: Empty title Attribute
An empty title is the same as having no title at all.
<iframe src="https://example.com/video-player" title=""></iframe>
Correct: Using the title Attribute
<iframe src="https://example.com/video-player" title="Product demo video player"></iframe>
Correct: Using aria-label
<iframe src="https://example.com/map" aria-label="Store location map"></iframe>
Correct: Using aria-labelledby
<h2 id="chat-heading">Live Chat Support</h2>
<iframe src="https://example.com/chat" aria-labelledby="chat-heading"></iframe>
Correct: Multiple Frames With Unique Titles
When a page contains several frames, each one should have a distinct, descriptive title.
<iframe src="/navigation" title="Site navigation menu"></iframe>
<iframe src="/content" title="Main article content"></iframe>
<iframe src="/ads" title="Sponsored advertisements"></iframe>
Correct: Matching the Frame Title to the Document Title
For the best experience, align the title attribute on the <iframe> with the <title> element inside the framed document.
<!-- Parent page -->
<iframe src="contact-form.html" title="Contact us form"></iframe>
<!-- contact-form.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>Contact us form</title>
</head>
<body>
<h1>Contact Us</h1>
<form>
<label for="email">Email</label>
<input type="email" id="email">
<button type="submit">Send</button>
</form>
</body>
</html>
Headings (h1 through h6) serve as a structural outline of your page. Sighted users can visually scan a page and understand its organization through font sizes and visual weight, but screen reader users depend entirely on properly marked-up heading levels to achieve the same understanding. When heading levels are skipped — for example, jumping from an h2 to an h4 — it creates confusion because users can't tell whether they missed a subsection or if the structure is simply incorrect.
This rule primarily affects users who are blind, deafblind, or have mobility impairments and rely on assistive technology to navigate. Screen readers provide keyboard shortcuts that let users jump between headings, effectively creating a table of contents for the page. If the heading hierarchy has gaps, users may waste time trying to find content they assume exists in the missing level, or they may misunderstand the relationship between sections.
This is a Deque best practice rule. While it aligns with the intent of WCAG Success Criterion 1.3.1 (Info and Relationships) and Success Criterion 2.4.6 (Headings and Labels), the sequential ordering of heading levels is not an explicit WCAG requirement. However, maintaining a logical heading order is widely considered essential for usable, accessible content.
How to Fix It
- Audit your heading structure. Review all headings on the page and verify they follow a sequential order. An
h1should be followed by anh2, anh2by anh3, and so on. You can go back up to a higher level at any time (e.g., fromh3back toh2), but you should never skip levels going down. - Start main content with an
h1. Screen reader users can jump directly to the firsth1on a page, so placing it at the beginning of your main content lets them skip navigation and other preamble. - Don't use headings for visual styling. If you need text to look bigger or bolder, use CSS instead of heading elements. Heading markup should only be used for actual headings that describe the content that follows.
- Read only the headings. A quick test: read through just the headings on your page. Do they give you a clear sense of the page's structure and content? If not, revise them.
Examples
Incorrect: Skipped Heading Levels
This example jumps from h1 to h3, skipping the h2 level entirely.
<h1>Photography Basics</h1>
<p>Learn the fundamentals of photography.</p>
<h3>Understanding ISO</h3>
<p>ISO controls the sensor's sensitivity to light.</p>
<h3>Choosing an Aperture</h3>
<p>Aperture affects depth of field and light intake.</p>
Correct: Sequential Heading Levels
The headings follow a logical order without skipping any levels.
<h1>Photography Basics</h1>
<p>Learn the fundamentals of photography.</p>
<h2>Understanding ISO</h2>
<p>ISO controls the sensor's sensitivity to light.</p>
<h2>Choosing an Aperture</h2>
<p>Aperture affects depth of field and light intake.</p>
Correct: Deeper Nesting with Proper Hierarchy
When content has subsections, each level increments by one. You can return to a higher level when starting a new major section.
<h1>Setting Exposure Manually on a Camera</h1>
<p>Manual exposure involves three key settings.</p>
<h2>Set the ISO</h2>
<p>Start by choosing an ISO value.</p>
<h3>Low Light Conditions</h3>
<p>Use a higher ISO in dim environments.</p>
<h3>Bright Light Conditions</h3>
<p>Use a lower ISO outdoors in sunlight.</p>
<h2>Choose an Aperture</h2>
<p>Aperture is measured in f-stops.</p>
<h2>Set a Shutter Speed</h2>
<p>Shutter speed controls motion blur.</p>
Incorrect: Using Headings for Visual Styling
Here, an h4 is used not because it fits the document hierarchy but because the developer wanted smaller text.
<h1>Our Services</h1>
<p>We offer a range of professional services.</p>
<h4>Contact us today for a free quote!</h4>
Correct: Using CSS Instead of Heading Markup for Styling
<h1>Our Services</h1>
<p>We offer a range of professional services.</p>
<p class="callout">Contact us today for a free quote!</p>
This rule is an informational check rather than a pass/fail test. It identifies elements whose content is hidden from both sighted users and assistive technologies, meaning automated tools like axe cannot inspect that content for issues. The goal is to ensure you don't overlook potentially inaccessible content simply because it's currently not visible.
Why this matters
When content is hidden using CSS properties like display: none or visibility: hidden, it is removed from the accessibility tree entirely. This means screen readers cannot access it, and automated testing tools cannot evaluate it. If that hidden content is later revealed — through user interaction, JavaScript toggling, or media queries — it must be fully accessible when it becomes visible.
Several groups of users can be affected by inaccessible hidden content:
- Screen reader users may encounter content that lacks proper labels, headings, or semantic structure once it's revealed.
- Keyboard-only users may find that revealed content contains focus traps or elements that aren't keyboard operable.
- Users with low vision or color blindness may encounter contrast or styling issues in content that was never tested because it was hidden during analysis.
If there's a compelling reason to hide content from sighted users, there's usually an equally compelling reason to hide it from screen reader users too. Conversely, when content is available to sighted users, it should also be available to assistive technology users.
This rule aligns with Deque best practices for thorough accessibility testing. While it doesn't map to a specific WCAG success criterion, failing to review hidden content could mask violations of criteria like 1.1.1 Non-text Content, 1.3.1 Info and Relationships, 2.1.1 Keyboard, 4.1.2 Name, Role, Value, and many others.
How to fix it
When axe flags hidden content, you need to:
- Identify the hidden elements reported by the rule.
- Trigger their display — interact with the page to make the content visible (e.g., open a modal, expand an accordion, hover over a tooltip).
- Run accessibility checks again on the now-visible content.
- Fix any issues found in that content, just as you would for any visible element.
- Verify hiding technique is appropriate — make sure content hidden with
display: noneorvisibility: hiddenis truly meant to be hidden from all users, including assistive technology users.
If you intend content to be visually hidden but still accessible to screen readers, use a visually-hidden CSS technique instead of display: none or visibility: hidden.
Examples
Hidden content that cannot be analyzed
This content is completely hidden from all users and automated tools:
<div style="display: none;">
<img src="chart.png">
<p>Quarterly revenue increased by 15%.</p>
</div>
The img element inside may be missing an alt attribute, but axe cannot detect this because the entire div is hidden. You must reveal this content and test it separately.
Hidden content revealed for testing and fixed
Once the content is made visible, you can identify and fix issues:
<div style="display: block;">
<img src="chart.png" alt="Bar chart showing quarterly revenue increased by 15%">
<p>Quarterly revenue increased by 15%.</p>
</div>
Using visibility: hidden
<nav style="visibility: hidden;">
<a href="/home">Home</a>
<a href="/about">About</a>
</nav>
This navigation is hidden from everyone. If it becomes visible through a JavaScript interaction, ensure it is tested in that visible state.
Visually hidden but accessible to screen readers
If you want content hidden visually but still available to assistive technologies, don't use display: none or visibility: hidden. Use a visually-hidden class instead:
<style>
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style>
<button>
<svg aria-hidden="true" focusable="false"><!-- icon --></svg>
<span class="visually-hidden">Close menu</span>
</button>
This approach keeps the text accessible to screen readers while hiding it visually. Axe can still analyze this content because it remains in the accessibility tree.
Interactive content that toggles visibility
When content is toggled dynamically, make sure both states are tested:
<button aria-expanded="false" aria-controls="panel1">Show details</button>
<div id="panel1" style="display: none;">
<p>Additional product details go here.</p>
</div>
<script>
const button = document.querySelector('button');
const panel = document.getElementById('panel1');
button.addEventListener('click', () => {
const expanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', String(!expanded));
panel.style.display = expanded ? 'none' : 'block';
});
</script>
To fully test this, click the button to reveal the panel content, then run your accessibility analysis again to check the revealed content for any violations.
Screen readers rely on distinct sound libraries for each language, with pronunciation rules, intonation patterns, and phonetics tailored to that specific language. When a user navigates to a page, the screen reader checks the lang attribute on the <html> element to determine which sound library to load. If no lang attribute is present, the screen reader falls back to whatever default language the user configured during setup. For someone who speaks multiple languages and browses websites in different languages, this creates a serious problem — the screen reader will attempt to read foreign-language content using the wrong pronunciation rules, producing garbled or incomprehensible speech.
This issue primarily affects blind and deafblind users who rely on screen readers, but it also impacts users with cognitive disabilities who may use text-to-speech tools. When text is read aloud with the wrong language rules, even simple words become unrecognizable, effectively making the content inaccessible.
Related WCAG Success Criteria
This rule maps to WCAG 2.0, 2.1, and 2.2 Success Criterion 3.1.1: Language of Page at Level A — the most fundamental level of conformance. This criterion requires that the default human language of each web page can be programmatically determined. It is also referenced by the Trusted Tester guidelines (11.A), EN 301 549, and RGAA.
How to Fix It
Add a lang attribute with a valid language code to the opening <html> element. The value should represent the primary language of the page content.
You can use a simple two-letter language code (like en for English, fr for French, or de for German), or you can be more specific with a regional dialect code like en-US for American English or fr-CA for Canadian French. A full list of valid language subtags is available in the IANA Language Subtag Registry.
If sections of your page contain content in a different language than the primary one, use the lang attribute on those specific elements so screen readers can switch sound libraries mid-page.
For languages written right to left (such as Arabic or Hebrew), also include the dir attribute with a value of rtl to indicate the text direction.
Examples
Incorrect: Missing lang attribute
This will trigger the rule because the <html> element has no lang attribute:
<html>
<head>
<title>My Website</title>
</head>
<body>
<h1>Welcome</h1>
</body>
</html>
Correct: lang attribute with a valid language code
<html lang="en">
<head>
<title>My Website</title>
</head>
<body>
<h1>Welcome</h1>
</body>
</html>
Correct: Using a regional dialect code
<html lang="en-US">
<head>
<title>My Website</title>
</head>
<body>
<h1>Welcome</h1>
</body>
</html>
Correct: Handling inline language changes
When a portion of the page is in a different language, use the lang attribute on the containing element:
<html lang="en">
<head>
<title>My Website</title>
</head>
<body>
<p>The French word for hello is <span lang="fr">bonjour</span>.</p>
</body>
</html>
Correct: Right-to-left language with dir attribute
<html lang="ar" dir="rtl">
<head>
<title>موقعي</title>
</head>
<body>
<h1>مرحبا</h1>
</body>
</html>
Screen readers rely on language-specific sound libraries to pronounce text accurately. Each language has its own pronunciation rules, intonation patterns, and phonetic characteristics, so screen readers load the appropriate library based on the declared language of the document. When the <html> element has no lang attribute or contains an invalid value — such as a misspelling or a fabricated code — the screen reader cannot determine which library to use. It defaults to whatever language the user configured during setup, which means content written in French might be read aloud using English pronunciation rules. The result is speech that sounds like a confusing, unintelligible accent.
This issue primarily affects blind users and deafblind users who rely on screen readers, but it also impacts users with cognitive disabilities who may use text-to-speech tools to aid comprehension. When pronunciation is wrong, understanding drops dramatically.
Related WCAG Success Criteria
This rule maps to WCAG Success Criterion 3.1.1: Language of Page (Level A), which requires that the default human language of each web page can be programmatically determined. This is a Level A requirement — the most fundamental level of accessibility — meaning it must be satisfied for basic compliance under WCAG 2.0, 2.1, and 2.2. It is also required by the Trusted Tester methodology, EN 301 549, and RGAA.
How to Fix It
- Add a
langattribute to the opening<html>element. - Set its value to a valid BCP 47 language tag that represents the primary language of the page.
- Double-check the spelling of the language code. Common valid codes include
"en"(English),"fr"(French),"es"(Spanish),"de"(German),"ja"(Japanese), and"ar"(Arabic). - You can optionally include a regional subtag, such as
"en-US"for American English or"fr-CA"for Canadian French. - For XHTML documents served as XML, use
xml:langinstead of or in addition tolang.
If the language changes within the document for specific passages, use the lang attribute on the appropriate element to indicate the switch. For right-to-left languages, also include the dir="rtl" attribute.
Examples
Incorrect: Missing lang attribute
<html>
<head>
<title>My Page</title>
</head>
<body>
<p>Welcome to my website.</p>
</body>
</html>
Without a lang attribute, screen readers cannot determine the page language and will default to the user's configured language, which may not match the content.
Incorrect: Invalid lang value
<html lang="english">
<head>
<title>My Page</title>
</head>
<body>
<p>Welcome to my website.</p>
</body>
</html>
The value "english" is not a valid BCP 47 language tag. The correct code for English is "en".
Correct: Valid lang attribute
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<p>Welcome to my website.</p>
</body>
</html>
Correct: Regional subtag
<html lang="fr-CA">
<head>
<title>Ma page</title>
</head>
<body>
<p>Bienvenue sur mon site web.</p>
</body>
</html>
Correct: Language change within the page
<html lang="en">
<head>
<title>Multilingual Page</title>
</head>
<body>
<p>This paragraph is in English.</p>
<p lang="es">Este párrafo está en español.</p>
</body>
</html>
Correct: Right-to-left language
<html lang="ar" dir="rtl">
<head>
<title>صفحتي</title>
</head>
<body>
<p>مرحبا بكم في موقعي.</p>
</body>
</html>
The dir="rtl" attribute ensures the text direction is correctly rendered for right-to-left languages like Arabic and Hebrew.
The lang attribute tells browsers and assistive technologies what language the content is written in. The xml:lang attribute serves the same purpose but comes from the XML/XHTML specification. When both attributes are present on the same element—most commonly the <html> element—they must agree on the base language. The "base language" is the primary language subtag (e.g., en in en-US or fr in fr-CA). If one attribute says en and the other says fr, assistive technologies receive conflicting information and cannot reliably determine how to process the content.
Who is affected
This issue primarily impacts screen reader users, including people who are blind, deafblind, or have cognitive disabilities. Screen readers maintain separate pronunciation libraries for each language. When they encounter a language declaration, they switch to the appropriate library so words are spoken with correct pronunciation and cadence. A mismatch between lang and xml:lang can cause the screen reader to select the wrong library or behave unpredictably, making the content difficult or impossible to understand.
Users who speak multiple languages and browse sites in different languages are especially vulnerable, as they rely on accurate language declarations to switch between language contexts.
Related WCAG success criteria
This rule relates to WCAG Success Criterion 3.1.1: Language of Page (Level A), which requires that the default human language of each web page can be programmatically determined. This criterion applies across WCAG 2.0, 2.1, and 2.2, as well as EN 301 549 and RGAA. When lang and xml:lang conflict, the programmatically determined language becomes ambiguous, violating this requirement.
How to fix it
- Check the
<html>element (and any other elements) for the presence of bothlangandxml:langattributes. - Ensure both attributes use the same base language. For example, if
lang="en", thenxml:langmust also start withen(e.g.,en,en-US, oren-GB). - If you don't need
xml:lang, the simplest fix is to remove it entirely. Thelangattribute alone is sufficient for HTML5 documents. - If you're serving XHTML, both attributes may be required. In that case, keep them in sync.
You can find valid language codes on the ISO 639 language codes reference page. Common codes include en for English, fr for French, es for Spanish, de for German, and ar for Arabic.
Examples
Incorrect: mismatched base languages
The lang attribute specifies English, but xml:lang specifies French. This creates a conflict that confuses assistive technologies.
<html lang="en" xml:lang="fr">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Correct: matching base languages
Both attributes specify English. The dialect subtag may differ, but the base language (en) is the same.
<html lang="en-US" xml:lang="en-GB">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Correct: identical values
The simplest and most reliable approach—use the exact same value for both attributes.
<html lang="en" xml:lang="en">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Correct: only lang attribute (HTML5)
In HTML5 documents, the xml:lang attribute is not required. Using lang alone avoids any possibility of a mismatch.
<html lang="en">
<head>
<title>My Page</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Correct: specifying language on inline elements
When content in a different language appears within the page, use the lang attribute on the appropriate element. If you also use xml:lang, make sure they match.
<p>Welcome to our site. <span lang="es" xml:lang="es">Bienvenidos a nuestro sitio.</span></p>
For right-to-left languages, also include the dir attribute:
<p lang="ar" xml:lang="ar" dir="rtl">مرحبا بكم في موقعنا</p>
When screen reader users navigate a page, they often pull up a list of all links to quickly scan available destinations. If that list contains several links all labeled "Read more," there's no way to tell them apart. This creates confusion and forces users to leave the link list, find each link in context, and read the surrounding content to understand its purpose. Users who are blind or deafblind are most affected, but this issue also impacts anyone who relies on link text to understand navigation options.
This rule relates to WCAG Success Criterion 2.4.9: Link Purpose (Link Only), a Level AAA requirement. It states that the purpose of each link should be determinable from the link text alone. While the related criterion 2.4.4 (Level A) allows link purpose to be determined from context, SC 2.4.9 sets a higher bar: each link's text must be self-explanatory on its own. Additionally, Success Criterion 3.2.4: Consistent Identification requires that components with the same functionality are identified consistently. Together, these criteria mean that links with the same name should go to the same place, and links that go to different places should have different names.
How to Fix the Problem
The key principle is straightforward: if two links have the same accessible name, they should serve the same purpose. If they serve different purposes, give them distinct accessible names.
Here are practical ways to fix this:
- Write descriptive link text. Instead of generic phrases like "Read more" or "Click here," write link text that describes the destination, such as "Read the accessibility policy" or "View January's meeting minutes."
- Use
aria-labelto differentiate links. When the visible text must be generic (e.g., for design reasons), usearia-labelto provide a unique, descriptive name for each link. - Use
aria-labelledbyto combine visible headings or other text with the link to form a unique accessible name. - Provide meaningful
alttext on image links. If a link contains only an image, the image'saltattribute becomes the link's accessible name. Make sure it describes the link's destination.
Examples
Incorrect: Multiple links with the same name going to different pages
In this example, two "Read more" links appear identical to assistive technology but lead to entirely different articles:
<h3>Neighborhood News</h3>
<p>Seminole tax hike: City managers propose a 75% increase in property taxes.
<a href="taxhike.html">Read more...</a>
</p>
<p>Baby Mayor: Voters elect the city's youngest mayor ever.
<a href="babymayor.html">Read more...</a>
</p>
A screen reader listing all links on this page would show two identical entries — "Read more..." — with no way to distinguish them.
Correct: Using aria-label to differentiate links
By adding an aria-label, each link gets a unique accessible name while keeping the visible text short:
<h3>Neighborhood News</h3>
<p>Seminole tax hike: City managers propose a 75% increase in property taxes.
<a href="taxhike.html" aria-label="Read more about Seminole tax hike">Read more...</a>
</p>
<p>Baby Mayor: Voters elect the city's youngest mayor ever.
<a href="babymayor.html" aria-label="Read more about Seminole's new baby mayor">Read more...</a>
</p>
Correct: Writing descriptive link text directly
The simplest approach is to make the link text itself descriptive:
<a href="routes.html">Current routes at Boulders Climbing Gym</a>
Correct: Descriptive alt text on an image link
When a link wraps an image, the alt attribute serves as the link's accessible name:
<a href="routes.html">
<img src="topo.gif" alt="Current routes at Boulders Climbing Gym">
</a>
Correct: Image and text together in a link
When a link contains both an image and text, use an empty alt attribute on the image to avoid redundancy. The visible text becomes the accessible name:
<a href="routes.html">
<img src="topo.gif" alt="">
Current routes at Boulders Climbing Gym
</a>
Correct: Links with the same name pointing to the same destination
If two links genuinely go to the same place, it's fine for them to share the same name:
<nav>
<a href="/contact">Contact us</a>
</nav>
<footer>
<a href="/contact">Contact us</a>
</footer>
Both links are labeled "Contact us" and both lead to the same contact page, so there is no ambiguity.
Correct: Using aria-labelledby to build a unique name from existing content
You can reference a nearby heading to create a unique accessible name without adding extra visible text:
<h3 id="tax-heading">Seminole Tax Hike</h3>
<p>City managers propose a 75% increase in property taxes.
<a href="taxhike.html" aria-labelledby="tax-heading tax-link">
<span id="tax-link">Read more</span>
</a>
</p>
<h3 id="mayor-heading">Baby Mayor Elected</h3>
<p>Voters elect the city's youngest mayor ever.
<a href="babymayor.html" aria-labelledby="mayor-heading mayor-link">
<span id="mayor-link">Read more</span>
</a>
</p>
The first link's accessible name resolves to "Seminole Tax Hike Read more" and the second to "Baby Mayor Elected Read more," making them distinguishable in a links list.
Validate at scale.
Ship accessible websites, faster.
Automated HTML & accessibility validation for large sites. Check thousands of pages against WCAG guidelines and W3C standards in minutes, not days.
Pro Trial
Full Pro access. Cancel anytime.
Start Pro Trial →Join teams across 40+ countries