Accessibility Guides for low vision
Learn how to identify and fix common accessibility issues flagged by Axe Core — so your pages are inclusive and usable for everyone. Also check our HTML Validation Guides.
The accesskey attribute provides a keyboard shortcut that lets users quickly activate or move focus to a specific element, typically by pressing the designated key in combination with a modifier key (such as Alt on Windows or Control + Option on macOS). When multiple elements share the same accesskey value, browsers handle the conflict inconsistently — some will focus the first matching element, others will cycle through matches, and some may ignore the duplicates entirely. This unpredictability undermines the purpose of access keys and creates a confusing experience.
This issue primarily affects users who rely on the keyboard to navigate, including people who are blind and use screen readers, users with low vision who cannot easily track a mouse pointer, and users with motor disabilities who depend on keyboards or keyboard-emulating input devices. For these users, a duplicated access key can mean they can never reach the intended element, or they reach the wrong one without realizing it.
This rule is classified as a Deque Best Practice. While it doesn’t map directly to a specific WCAG success criterion, it supports the broader principles behind WCAG 2.1.1 Keyboard (all functionality must be operable via keyboard) and WCAG 4.1.2 Name, Role, Value (user interface components must behave predictably). Duplicate access keys undermine keyboard operability by introducing unreliable shortcuts.
How to fix it
-
Find all elements on the page that use the
accesskeyattribute. -
Identify duplicates — any
accesskeyvalue that appears on more than one element. - Assign a unique value to each element so no two share the same key.
-
Avoid conflicts with default browser and screen reader shortcuts. For example, many browsers reserve
Alt + Dfor the address bar andAlt + Ffor the File menu.
A note of caution about accesskey
While fixing duplicates is important, be aware that the accesskey attribute has significant limitations in practice:
- Inconsistent browser support: Not all browsers implement access keys the same way, and modifier key combinations vary across operating systems.
- Conflict risk: Developer-defined access keys can override default browser or assistive technology shortcuts, potentially breaking expected functionality.
- Discoverability: Access keys are invisible to most users unless explicitly documented on the page.
-
Localization issues: A key that makes sense as a mnemonic in one language (e.g.,
sfor “Search”) may not work in another.
For these reasons, many accessibility experts recommend avoiding accesskey altogether and instead relying on well-structured headings, landmarks, and skip links for efficient keyboard navigation.
Examples
Incorrect: duplicate accesskey values
Both links use accesskey="g", so the browser cannot determine which element to activate.
<a href="https://google.com" accesskey="g">Link to Google</a>
<a href="https://github.com" accesskey="g">Link to GitHub</a>
Correct: unique accesskey values
Each link has a distinct accesskey value, so keyboard shortcuts work as expected.
<a href="https://google.com" accesskey="g">Link to Google</a>
<a href="https://github.com" accesskey="h">Link to GitHub</a>
Correct: removing accesskey in favor of better patterns
If access keys aren’t essential, consider removing them and providing clear navigation structure instead.
<nav aria-label="Main navigation">
<a href="https://google.com">Link to Google</a>
<a href="https://github.com">Link to GitHub</a>
</nav>
When you assign a role like link, button, or menuitem to an element, you are telling the browser and assistive technologies that this element is an interactive command. Screen readers rely on the accessible name of these elements to communicate their purpose to the user. If no accessible name exists, a screen reader might announce something like “button” or “link” with no additional context — leaving the user with no way to understand what the control does.
This issue primarily affects users who are blind or have low vision and rely on screen readers, but it also impacts users with mobility impairments who may use voice control software to activate elements by name. If there is no name, voice control users cannot target the element with a spoken command.
Related WCAG Success Criteria
This rule maps to WCAG Success Criterion 4.1.2: Name, Role, Value (Level A). This criterion requires that all user interface components have a name that can be programmatically determined. It applies across WCAG 2.0, 2.1, and 2.2, and is also referenced in EN 301 549 (9.4.1.2), Trusted Tester guidelines, and RGAA.
The Trusted Tester guidelines further specify that the purpose of each link or button must be determinable from some combination of its text, accessible name, accessible description, or programmatically determined context.
How to Fix It
Ensure that every element with role="link", role="button", or role="menuitem" has an accessible name through one of these methods:
- Inner text content — Place readable text inside the element.
-
aria-labelattribute — Add a non-emptyaria-labelwith a descriptive name. -
aria-labelledbyattribute — Point to theidof another element that contains visible, non-empty text. -
titleattribute — Use atitleattribute as a fallback (thougharia-labelor visible text is preferred).
When possible, prefer using native HTML elements (<a>, <button>) over custom ARIA roles, as they come with built-in accessibility behaviors. If you must use ARIA roles, always make sure the accessible name is clear and describes the action or destination.
Examples
Incorrect: No accessible name
These elements will be flagged because screen readers cannot determine their purpose.
<!-- Empty element with no text or label -->
<div role="link"></div>
<!-- Empty aria-label provides no name -->
<div role="button" aria-label=""></div>
<!-- aria-labelledby points to a non-existent element -->
<div role="menuitem" aria-labelledby="nonexistent"></div>
<!-- aria-labelledby points to an empty element -->
<div role="link" aria-labelledby="empty-label"></div>
<div id="empty-label"></div>
Correct: Accessible name provided
Each of these elements has a discernible name that screen readers can announce.
<!-- Inner text content -->
<div role="link" tabindex="0">Visit our help center</div>
<!-- aria-label attribute -->
<div role="button" tabindex="0" aria-label="Close dialog"></div>
<!-- aria-labelledby pointing to visible text -->
<div role="menuitem" tabindex="0" aria-labelledby="menu-label">
<span id="menu-label">Save document</span>
</div>
<!-- Combination of aria-label and inner text -->
<div role="link" tabindex="0" aria-label="Learn more about pricing">
Learn more
</div>
<!-- title attribute as a fallback -->
<div role="button" tabindex="0" title="Submit form"></div>
Preferred: Use native HTML elements
Native elements handle naming and keyboard behavior automatically, reducing the chance of accessibility issues.
<a href="/help">Visit our help center</a>
<button type="button">Close dialog</button>
Note: When testing with RGAA, issues reported by this rule may need to be mapped to a different RGAA test, such as 6.2.1 for links.
When a dialog appears on screen, assistive technologies announce it to the user along with its accessible name. This name gives essential context — it tells the user what the dialog is about, such as “Confirm deletion” or “Sign in to your account.” Without an accessible name, screen reader users hear that a dialog has opened but have no way to understand its purpose, which can be disorienting and make the interface difficult to use.
This issue primarily affects users who are blind or have low vision and rely on screen readers, as well as users with mobility impairments who navigate with assistive technology. It is flagged as a serious issue by the axe accessibility engine and aligns with Deque’s accessibility best practices.
How to Fix It
Ensure every element with role="dialog" or role="alertdialog" has an accessible name using one of these methods:
-
aria-label— Provide a concise, descriptive name directly on the dialog element. -
aria-labelledby— Reference theidof a visible element (typically a heading) inside the dialog that serves as its title. -
title— Use the HTMLtitleattribute as a fallback, thougharia-labeloraria-labelledbyare preferred.
Whichever method you choose, make sure the name clearly describes the dialog’s purpose. Avoid empty strings or references to elements that don’t exist or have no text content.
Examples
Incorrect: Dialog with No Accessible Name
The dialog has no aria-label, aria-labelledby, or title, so screen readers cannot announce its purpose.
<div role="dialog">
<h2>Unsaved Changes</h2>
<p>You have unsaved changes. Do you want to save before leaving?</p>
<button>Save</button>
<button>Discard</button>
</div>
Incorrect: Empty aria-label
An empty aria-label provides no useful information to assistive technologies.
<div role="alertdialog" aria-label="">
<p>An error occurred while saving your file.</p>
<button>OK</button>
</div>
Incorrect: aria-labelledby Pointing to a Nonexistent or Empty Element
If the referenced element doesn’t exist or contains no text, the dialog still has no accessible name.
<div role="dialog" aria-labelledby="dialog-title">
<p>Please confirm your selection.</p>
<button>Confirm</button>
</div>
<!-- No element with id="dialog-title" exists -->
<div role="dialog" aria-labelledby="dialog-title">
<span id="dialog-title"></span>
<p>Please confirm your selection.</p>
<button>Confirm</button>
</div>
Correct: Using aria-labelledby to Reference a Heading
The aria-labelledby attribute points to the dialog’s visible heading, giving it a clear accessible name.
<div role="dialog" aria-labelledby="dialog-title">
<h2 id="dialog-title">Unsaved Changes</h2>
<p>You have unsaved changes. Do you want to save before leaving?</p>
<button>Save</button>
<button>Discard</button>
</div>
Correct: Using aria-label
The aria-label attribute provides a concise name directly on the dialog.
<div role="alertdialog" aria-label="File save error">
<p>An error occurred while saving your file. Please try again.</p>
<button>Retry</button>
<button>Cancel</button>
</div>
Correct: Using the title Attribute
The title attribute works as a fallback naming mechanism, though aria-label or aria-labelledby are generally preferred because title has inconsistent support across assistive technologies.
<div role="dialog" title="Export Settings">
<p>Choose a format for your export.</p>
<select>
<option>PDF</option>
<option>CSV</option>
</select>
<button>Export</button>
<button>Cancel</button>
</div>
The aria-hidden="true" attribute tells assistive technologies to ignore an element and all of its descendants. This is useful for hiding purely decorative content — such as icon fonts or redundant visuals — that would clutter the screen reader experience. However, a serious problem arises when focusable elements like links, buttons, form inputs, or elements with tabindex="0" exist inside an aria-hidden="true" container.
When this happens, keyboard users can still Tab to those elements, but screen readers won’t announce them. The user lands on what feels like an invisible, unlabeled control. They have no way to know what the element is or what it does. This affects blind users, deafblind users, users with low vision who rely on screen readers, and mobility-impaired users who navigate exclusively by keyboard.
It’s also important to understand that aria-hidden="false" on a descendant does not override aria-hidden="true" on an ancestor. Once a parent is hidden from the accessibility tree, all children remain hidden regardless of their own aria-hidden value. Any focusable children inside that subtree still create the same problem.
Related WCAG Success Criteria
This rule primarily relates to WCAG Success Criterion 4.1.2: Name, Role, Value (Level A). This criterion requires that for all user interface components, the name and role can be programmatically determined, and states, properties, and values can be programmatically set. A focusable element inside an aria-hidden="true" container violates this because its name and role are stripped from the accessibility API while it remains reachable via keyboard — making it impossible for assistive technologies to convey its purpose. This rule is flagged across WCAG 2.0, 2.1, and 2.2 at Level A, as well as in Trusted Tester, EN 301 549, and RGAA guidelines.
How to Fix It
There are several strategies to resolve this issue:
-
Remove
aria-hidden="true"from elements that contain focusable children, if those children need to be interactive. -
Remove focusable elements from inside the
aria-hidden="true"container if the entire section is truly meant to be hidden. -
Make focusable elements unfocusable by adding
tabindex="-1"to links, buttons, or other interactive elements inside the hidden container. -
Use the
disabledattribute on form controls (notaria-disabled, which does not actually prevent focus). -
Hide elements with CSS using
display: noneorvisibility: hidden, which removes them from both the accessibility tree and the focus order simultaneously.
If you need to hide content from assistive technologies, ensure equivalent meaning and functionality is still available through other accessible means.
Examples
Incorrect: Focusable link inside aria-hidden="true"
The link is removed from the accessibility tree but still receives keyboard focus.
<div aria-hidden="true">
<a href="/home">Home</a>
</div>
Incorrect: Offscreen focusable link inside aria-hidden="true"
Moving a link offscreen does not remove it from the focus order.
<div aria-hidden="true">
<a href="/" style="position:absolute; top:-999em">Link</a>
</div>
Incorrect: Using aria-disabled instead of disabled
The aria-disabled attribute does not actually prevent the input from receiving focus.
<div aria-hidden="true">
<input aria-disabled="true" />
</div>
Incorrect: Element with tabindex="0" and aria-hidden="true"
Adding tabindex="0" makes a normally non-focusable element focusable, creating a conflict with aria-hidden="true".
<p tabindex="0" aria-hidden="true">Some descriptive text</p>
Incorrect: Trying to override aria-hidden on a descendant
Setting aria-hidden="false" on a child does not re-expose it when a parent has aria-hidden="true". The button remains hidden from assistive technologies but still receives focus.
<div aria-hidden="true">
<div aria-hidden="false">
<button>Submit</button>
</div>
</div>
Incorrect: Focusable <summary> inside aria-hidden="true"
The <summary> element is natively focusable.
<details aria-hidden="true">
<summary>More info</summary>
<p>Additional details here.</p>
</details>
Correct: Non-focusable content inside aria-hidden="true"
A paragraph with no interactive elements is safe to hide.
<p aria-hidden="true">Decorative description text</p>
Correct: Focusable elements hidden with CSS display: none
Using display: none removes the link from both the focus order and the accessibility tree.
<div aria-hidden="true">
<a href="/" style="display:none">Link</a>
</div>
Correct: Focusable elements made unfocusable with tabindex="-1"
Adding tabindex="-1" removes the button from the tab order.
<div aria-hidden="true">
<button tabindex="-1">Close</button>
</div>
Correct: Form input properly disabled with the disabled attribute
The disabled attribute prevents the input from receiving focus entirely.
<input disabled aria-hidden="true" />
Correct: Removing aria-hidden and keeping elements interactive
If the content needs to be focusable, simply don’t hide it from assistive technologies.
<div>
<a href="/home">Home</a>
</div>
The meter role in ARIA represents a scalar measurement within a known range — think of a gauge showing a value like CPU temperature, password strength, or storage capacity. When a screen reader encounters an element with role="meter", it announces the element as a meter, but without an accessible name, it cannot convey what is being measured. The user hears something like “meter” with no context, which is effectively meaningless.
This issue primarily affects users who are blind or have low vision and rely on screen readers, as well as users with mobility impairments who may navigate via assistive technologies. It relates to WCAG 2.0/2.1/2.2 Success Criterion 1.1.1: Non-text Content (Level A), which requires that all non-text content has a text alternative that serves an equivalent purpose. A meter without a name fails to provide this text alternative.
How to Fix
Ensure every element with role="meter" has a clear, descriptive accessible name using one of these methods:
-
aria-label— Add descriptive text directly to the element. -
aria-labelledby— Reference another visible element that contains the label text. The referenced element must exist and contain non-empty text. -
title— Use thetitleattribute as a fallback naming method (thougharia-labeloraria-labelledbyare generally preferred).
The name should clearly describe what the meter is measuring so users understand its purpose in context.
Examples
Incorrect: Meter with no accessible name
The following meter has no name at all. A screen reader will announce it as a meter but cannot tell the user what it measures.
<div role="meter" aria-valuemin="0" aria-valuemax="100" aria-valuenow="75"></div>
Incorrect: Empty aria-label
An empty aria-label is equivalent to having no name.
<div role="meter" aria-label="" aria-valuemin="0" aria-valuemax="100" aria-valuenow="75"></div>
Incorrect: aria-labelledby referencing a nonexistent or empty element
If the referenced element doesn’t exist or has no text content, the meter still lacks an accessible name.
<div role="meter" aria-labelledby="nonexistent" aria-valuemin="0" aria-valuemax="100" aria-valuenow="75"></div>
<div role="meter" aria-labelledby="empty-label" aria-valuemin="0" aria-valuemax="100" aria-valuenow="75"></div>
<div id="empty-label"></div>
Correct: Using aria-label
<div role="meter" aria-label="Disk usage" aria-valuemin="0" aria-valuemax="100" aria-valuenow="75"></div>
Correct: Using aria-labelledby
<span id="meter-label">Battery level</span>
<div role="meter" aria-labelledby="meter-label" aria-valuemin="0" aria-valuemax="100" aria-valuenow="42"></div>
Correct: Using the title attribute
<div role="meter" title="Signal strength" aria-valuemin="0" aria-valuemax="5" aria-valuenow="3"></div>
Correct: Using the native <meter> element with a <label>
When possible, prefer the native HTML <meter> element, which has built-in semantics and can be associated with a <label>.
<label for="fuel">Fuel level</label>
<meter id="fuel" min="0" max="100" value="68">68%</meter>
When a progress bar lacks an accessible name, users who rely on assistive technologies have no way to distinguish it from other progress bars on the page or understand its purpose. Imagine a page with two progress bars—one for a file upload and one for a software installation. Without accessible names, a screen reader user would hear “progress bar” twice with no context for either. This issue primarily affects users who are blind or have low vision, as well as users with mobility impairments who may navigate using voice commands that reference element names.
This rule relates to WCAG Success Criterion 1.1.1: Non-text Content (Level A), which requires that all non-text content has a text alternative that serves the equivalent purpose. A progress bar is a non-text indicator of status, so it needs a text-based name to convey its meaning to assistive technology users. This criterion applies across WCAG 2.0, 2.1, and 2.2 at Level A, meaning it is a baseline requirement.
How to Fix
Ensure every element with role="progressbar" has a meaningful accessible name using one of these techniques:
-
aria-label— Provide a concise, descriptive label directly on the element. -
aria-labelledby— Reference theidof another element that contains visible label text. -
title— Use thetitleattribute as a fallback (thougharia-labeloraria-labelledbyare preferred).
The name should clearly describe what process or task the progress bar represents, such as “File upload progress” or “Installation progress.”
Examples
Failing: Progress bar with no accessible name
A progress bar with no labeling attributes is announced generically with no context.
<div role="progressbar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100">
</div>
Failing: Empty aria-label
An empty aria-label provides no name, so the progress bar remains unlabeled.
<div role="progressbar" aria-label="" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100">
</div>
Failing: aria-labelledby pointing to a nonexistent or empty element
If the referenced element doesn’t exist or has no text content, the progress bar still has no accessible name.
<div role="progressbar" aria-labelledby="missing-id" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100">
</div>
<!-- Or referencing an empty element -->
<div role="progressbar" aria-labelledby="empty-label" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100">
</div>
<div id="empty-label"></div>
Passing: Using aria-label
<div role="progressbar" aria-label="File upload progress" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100">
</div>
A screen reader will announce something like: “File upload progress, progress bar, 50%.”
Passing: Using aria-labelledby
<h3 id="upload-heading">Uploading resume.pdf</h3>
<div role="progressbar" aria-labelledby="upload-heading" aria-valuenow="70" aria-valuemin="0" aria-valuemax="100">
</div>
This approach is especially useful when a visible heading or label already describes the progress bar, keeping the visual and accessible names in sync.
Passing: Using title
<div role="progressbar" title="Installation progress" aria-valuenow="90" aria-valuemin="0" aria-valuemax="100">
</div>
The title attribute works as a naming mechanism, but note that it may also produce a tooltip on hover. Prefer aria-label or aria-labelledby when possible for a more consistent experience across assistive technologies.
Tooltips are supplementary text elements that appear when a user hovers over or focuses on a control. They typically provide descriptions, labels, or additional context. When an element has role="tooltip", assistive technologies recognize it as a tooltip and attempt to announce its name to the user.
If a tooltip lacks an accessible name, screen reader users hear something like “tooltip” with no accompanying description. This means they miss the very information the tooltip was designed to provide. Users affected include:
- Blind users who rely entirely on screen readers to access tooltip content.
- Low vision users who may use screen readers in combination with magnification and depend on announced text.
- Mobility-impaired users who navigate with keyboards or alternative input devices and rely on programmatic relationships to understand UI elements.
This rule relates to WCAG 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 applies across WCAG 2.0, 2.1, and 2.2, as well as EN 301 549 (guideline 9.4.1.2). Since this is a Level A requirement, it represents the minimum baseline for accessibility compliance.
How to Fix It
Ensure every element with role="tooltip" has a discernible accessible name through one of these methods:
- Inner text content — Place readable text directly inside the tooltip element.
-
aria-labelattribute — Set a non-emptyaria-labelwith a clear description. -
aria-labelledbyattribute — Reference another element’sidthat contains visible, non-empty text. -
titleattribute — Provide a non-emptytitleon the tooltip element (thougharia-labelor inner text are generally preferred).
The accessible name should be concise and clearly describe the information the tooltip is meant to communicate.
Examples
Incorrect: Empty Tooltip with No Accessible Name
The tooltip has no text content and no naming attribute, so screen readers cannot announce anything meaningful.
<button aria-describedby="tip1">Settings</button>
<div role="tooltip" id="tip1"></div>
Incorrect: Empty aria-label
An empty aria-label does not provide an accessible name.
<button aria-describedby="tip2">Save</button>
<div role="tooltip" id="tip2" aria-label=""></div>
Incorrect: aria-labelledby Pointing to a Non-Existent or Empty Element
If the referenced element doesn’t exist or has no text content, the tooltip has no accessible name.
<button aria-describedby="tip3">Delete</button>
<div role="tooltip" id="tip3" aria-labelledby="nonexistent"></div>
<button aria-describedby="tip4">Delete</button>
<div role="tooltip" id="tip4" aria-labelledby="empty-label"></div>
<div id="empty-label"></div>
Correct: Tooltip with Inner Text
The simplest approach — place descriptive text directly inside the tooltip element.
<button aria-describedby="tip5">Save</button>
<div role="tooltip" id="tip5">Save your current progress</div>
Correct: Tooltip with aria-label
Use aria-label when the tooltip’s visual content differs from what you want screen readers to announce, or when the tooltip is styled in a way that doesn’t use direct text content.
<button aria-describedby="tip6">Settings</button>
<div role="tooltip" id="tip6" aria-label="Open application settings"></div>
Correct: Tooltip with aria-labelledby
Reference another element that contains the descriptive text.
<button aria-describedby="tip7">Delete</button>
<div role="tooltip" id="tip7" aria-labelledby="tip7-label"></div>
<span id="tip7-label">Permanently delete this item</span>
Correct: Tooltip with title Attribute
The title attribute provides an accessible name as a fallback, though inner text or aria-label are generally more reliable across assistive technologies.
<button aria-describedby="tip8">Print</button>
<div role="tooltip" id="tip8" title="Print the current document"></div>
The treeitem role represents an item within a hierarchical tree widget, commonly used for file explorers, nested navigation menus, or collapsible category lists. When a treeitem lacks an accessible name, screen readers announce something like “tree item” with no further context, making it impossible for users who rely on assistive technology to distinguish one item from another or understand the tree’s structure.
This issue primarily affects screen reader users, but it can also impact users of voice control software who need to reference elements by name to interact with them.
This rule relates to WCAG 2.1 Success Criterion 4.1.2 (Name, Role, Value), which requires that all user interface components have a name that can be programmatically determined. It also supports Success Criterion 1.3.1 (Info and Relationships), ensuring that information conveyed through structure is available to all users.
How to Fix It
You can give a treeitem an accessible name in several ways:
- Inner text content — Place descriptive text directly inside the element.
-
aria-label— Add anaria-labelattribute with a descriptive string. -
aria-labelledby— Reference another element’sidthat contains the label text. -
titleattribute — Use thetitleattribute as a last resort (less reliable across assistive technologies).
The accessible name should be concise and clearly describe the item’s purpose or content.
Examples
Incorrect: treeitem with no accessible name
<ul role="tree">
<li role="treeitem"></li>
<li role="treeitem"></li>
</ul>
Screen readers announce these items as “tree item” with no distinguishing label.
Incorrect: treeitem with only a non-text child and no label
<ul role="tree">
<li role="treeitem">
<span class="icon-folder"></span>
</li>
</ul>
If the span renders only a CSS icon and contains no text, the treeitem still has no accessible name.
Correct: treeitem with visible text content
<ul role="tree">
<li role="treeitem">Documents</li>
<li role="treeitem">Photos</li>
<li role="treeitem">Music</li>
</ul>
Correct: treeitem with aria-label
<ul role="tree">
<li role="treeitem" aria-label="Documents">
<span class="icon-folder"></span>
</li>
<li role="treeitem" aria-label="Photos">
<span class="icon-folder"></span>
</li>
</ul>
Correct: treeitem with aria-labelledby
<ul role="tree">
<li role="treeitem" aria-labelledby="item1-label">
<span class="icon-folder"></span>
<span id="item1-label">Documents</span>
</li>
<li role="treeitem" aria-labelledby="item2-label">
<span class="icon-folder"></span>
<span id="item2-label">Photos</span>
</li>
</ul>
Correct: Nested treeitem elements with accessible names
<ul role="tree">
<li role="treeitem" aria-expanded="true">
Documents
<ul role="group">
<li role="treeitem">Resume.pdf</li>
<li role="treeitem">Cover Letter.docx</li>
</ul>
</li>
<li role="treeitem" aria-expanded="false">
Photos
</li>
</ul>
Every treeitem in the tree — including nested ones — must have an accessible name. When building tree widgets, also ensure proper keyboard interaction (arrow keys for navigation, Enter/Space for activation) and correct use of aria-expanded on parent items that contain child groups.
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.
The <blink> element was once used to draw attention to content by making it flash repeatedly. While it may have seemed like an eye-catching effect, it creates serious accessibility barriers for multiple groups of users. The element has been deprecated from the HTML specification, and although most modern browsers no longer render the blinking effect, the element should still be removed from your markup to ensure compliance and avoid issues in any environment that might still support it.
Why this is an accessibility problem
Blinking content affects several groups of users:
- Users with cognitive disabilities may find blinking text distracting or incomprehensible. The constant flashing can make it very difficult to focus on and understand the content.
- Users with low vision struggle to read text that appears and disappears rapidly. The intermittent visibility makes the content effectively unreadable.
- Users with motor disabilities may have difficulty clicking on blinking links or buttons, since the target is not consistently visible and requires precise timing to activate.
- Users with photosensitive conditions can experience discomfort or, in extreme cases, seizures from flashing content, depending on the frequency.
This rule relates to WCAG 2.2 Success Criterion 2.2.2: Pause, Stop, Hide (Level A), which requires that for any moving, blinking, or scrolling content that starts automatically and lasts more than five seconds, users must be able to pause, stop, or hide it. Since the <blink> element provides no mechanism for users to control the flashing, it fails this criterion outright.
This rule also applies to Section 508 §1194.22(j), which states that pages must be designed to avoid causing the screen to flicker with a frequency greater than 2 Hz and lower than 55 Hz.
How to fix it
-
Remove all
<blink>elements from your HTML. Replace them with standard elements like<span>,<strong>, or<em>. -
Remove
text-decoration: blink;from your CSS, as this is the CSS equivalent of the<blink>element. - Use alternative emphasis techniques to make content stand out — bold text, color, larger font size, borders, background highlights, or icons.
Important: Many modern browsers silently ignore the <blink> element, so the text won’t visually blink even though the element is present in the source code. Don’t rely on how the page looks in the browser to determine whether <blink> tags exist. Always check the actual HTML source.
Examples
Incorrect: using the <blink> element
<p><blink>Moving Sale Thursday!</blink></p>
This causes the text to flash on and off (in browsers that support it), making it inaccessible.
Incorrect: using text-decoration: blink in CSS
<style>
.attention {
text-decoration: blink;
}
</style>
<h2 class="attention">Limited Time Offer!</h2>
The CSS text-decoration: blink value achieves the same inaccessible effect as the <blink> element.
Correct: using static visual emphasis
<p><strong>Moving Sale Thursday!</strong></p>
Using <strong> makes the text bold and conveys emphasis to screen readers without any flashing.
Correct: using CSS for visual emphasis without blinking
<style>
.highlight {
background-color: #fff3cd;
border-left: 4px solid #ffc107;
padding: 8px 12px;
font-weight: bold;
}
</style>
<p class="highlight">Limited Time Offer!</p>
This draws attention to the content using color, a border, and bold text — all without any flashing or blinking, keeping the content readable and accessible for everyone.
Color contrast is one of the most impactful accessibility considerations on the web. The enhanced contrast requirements go beyond the minimum AA thresholds (4.5:1 and 3:1) to provide an even higher level of readability. These stricter ratios benefit a broad range of users, including people with low vision, color blindness, and age-related vision decline. Nearly three times as many people experience low vision compared to total blindness, and roughly 8% of men and 0.4% of women in the United States have some form of color vision deficiency. For these users, text that is too close in brightness to its background becomes difficult or impossible to read.
People with low contrast sensitivity perceive everything at roughly the same brightness, making it hard to distinguish outlines, edges, and details. Enhancing the contrast ratio between text and its background ensures that content remains legible across a wide range of visual abilities and environmental conditions, such as bright sunlight on a mobile screen.
This rule relates to WCAG Success Criterion 1.4.6: Contrast (Enhanced) at the AAA level. While AAA conformance is not typically required for legal compliance, meeting these thresholds represents a best practice that significantly improves readability for all users.
How the Rule Works
The color-contrast-enhanced rule from axe-core examines each text element on the page and calculates the contrast ratio between the computed foreground text color and the computed background color. It then checks whether the ratio meets the enhanced AAA thresholds:
- Small text: at least 7:1
- Large text: at least 4.5:1 (large text is defined as 18pt / 24px regular weight, or 14pt / 19px bold)
The rule accounts for background color transparency and opacity. However, certain scenarios are harder to evaluate automatically and may be flagged as needing manual review:
- Foreground and background colors that are identical (1:1 ratio)
- CSS background gradients
-
Background colors applied via CSS pseudo-elements (e.g.,
::before,::after) - Background colors created using CSS borders
- Overlap by another positioned element in the foreground
- Elements moved off-screen via CSS
The rule does not check text elements that have a background-image, are visually hidden by other elements, or are images of text. It also ignores child elements of disabled controls to avoid false positives.
How to Fix the Problem
- Identify the failing element and note the current foreground and background colors.
- Adjust the text color, background color, or both until the contrast ratio meets at least 7:1 for small text or 4.5:1 for large text.
- Use a contrast checking tool such as the axe DevTools browser extension, the WebAIM Contrast Checker, or the built-in contrast picker in Chrome DevTools to verify the new colors.
- Test across states — make sure hover, focus, active, and visited states also meet the required ratios.
- Check transparent and semi-transparent colors carefully, as the effective contrast depends on what is rendered behind the element.
Examples
Failing: Insufficient contrast for small text
This light gray text on a white background has a contrast ratio of approximately 2.8:1, which fails both the AA minimum and the AAA enhanced threshold.
<p style="color: #999999; background-color: #ffffff;">
This text is hard to read for many users.
</p>
Fixed: Enhanced contrast for small text
Darkening the text color to #595959 achieves a contrast ratio of approximately 7:1 against the white background, meeting the AAA enhanced requirement.
<p style="color: #595959; background-color: #ffffff;">
This text meets the enhanced contrast requirement.
</p>
Failing: Large text that does not meet the 4.5:1 threshold
Even though large text has a lower threshold, this combination still falls short at roughly 3:1.
<h1 style="font-size: 24px; color: #888888; background-color: #ffffff;">
Large heading with poor contrast
</h1>
Fixed: Large text meeting the enhanced 4.5:1 threshold
Changing the heading color to #767676 or darker brings the ratio to at least 4.5:1.
<h1 style="font-size: 24px; color: #636363; background-color: #ffffff;">
Large heading with enhanced contrast
</h1>
Failing: Semi-transparent text reducing effective contrast
Using rgba with reduced opacity lowers the effective contrast ratio below the required threshold.
<p style="color: rgba(0, 0, 0, 0.4); background-color: #ffffff;">
Semi-transparent text that lacks sufficient contrast.
</p>
Fixed: Increasing opacity to restore contrast
Raising the alpha value ensures the rendered color has enough contrast against the background.
<p style="color: rgba(0, 0, 0, 0.82); background-color: #ffffff;">
This semi-transparent text now meets enhanced contrast.
</p>
Color contrast is one of the most common accessibility barriers on the web. When text doesn’t stand out enough from its background, it becomes difficult or impossible to read for many users. People with low vision experience reduced contrast sensitivity, meaning everything appears roughly the same brightness, making it hard to distinguish outlines, edges, and details. Approximately 1 in 12 people cannot see the full spectrum of colors — about 8% of men and 0.4% of women in the United States have some form of color vision deficiency. Nearly three times as many people have low vision compared to total blindness. Without sufficient contrast, these users simply cannot read your content.
This rule maps to WCAG 2.1 Success Criterion 1.4.3: Contrast (Minimum), which is a Level AA requirement. It is also referenced in WCAG 2.0, WCAG 2.2, the Trusted Tester methodology, EN 301 549, and RGAA. The user impact is considered serious because insufficient contrast directly prevents users from perceiving text content.
How Contrast Ratios Work
Contrast ratio is calculated by comparing the relative luminance of two colors on a scale from 1:1 (no contrast, e.g., white on white) to 21:1 (maximum contrast, black on white). WCAG defines two thresholds:
- Normal text (below 18pt or below 14pt bold): minimum 4.5:1 contrast ratio
- Large text (at least 18pt / 24px, or at least 14pt bold / 19px): minimum 3:1 contrast ratio
“Large text” is defined this way because larger characters have wider strokes that are easier to read at lower contrast levels.
What This Rule Checks
The color-contrast rule in axe-core examines each text element on the page and compares the computed foreground text color against the computed background color. It accounts for background color transparency and opacity. Elements that are found to have a 1:1 contrast ratio are flagged as “incomplete” and require manual review.
This rule does not flag:
-
Text elements with a
background-image(these require manual testing) - Text that is visually hidden by other overlapping elements
- Images of text
- Text inside disabled controls (including child elements of disabled buttons)
Some foreground scenarios are harder for automated tools to evaluate, including CSS gradients, pseudo-element backgrounds, backgrounds created with CSS borders, and elements repositioned off-screen with CSS.
How to Fix It
- Identify the failing elements by running axe. Each violation will report the current contrast ratio and the colors involved.
- Adjust the foreground color, background color, or both until the required ratio is met (4.5:1 for normal text, 3:1 for large text).
- Use a contrast checker tool like the axe DevTools browser extension, the WebAIM Contrast Checker, or the built-in color contrast analyzer in browser developer tools to test color combinations before deploying.
- Test with real content — sometimes dynamic content or themed components produce contrast issues that static checks miss.
Examples
Insufficient contrast (fails)
This light gray text on a white background has a contrast ratio of approximately 2.6:1, which fails the 4.5:1 requirement.
<p style="color: #aaaaaa; background-color: #ffffff;">
This text is hard to read.
</p>
Sufficient contrast (passes)
Darkening the text color to #595959 against a white background achieves a contrast ratio of approximately 7:1, meeting the requirement.
<p style="color: #595959; background-color: #ffffff;">
This text is easy to read.
</p>
Large text with lower contrast requirement (passes)
Large text (18pt or larger) only needs a 3:1 contrast ratio. This example uses #767676 on white, which has a ratio of approximately 4.5:1 — well above the 3:1 threshold for large text.
<h1 style="font-size: 24pt; color: #767676; background-color: #ffffff;">
Large heading text
</h1>
Semi-transparent background (fails)
Transparency can reduce effective contrast. Here, the semi-transparent white background doesn’t provide enough contrast depending on what’s behind it.
<div style="background-color: #cccccc;">
<p style="color: #777777; background-color: rgba(255, 255, 255, 0.3);">
Text with a semi-transparent background.
</p>
</div>
Semi-transparent background fixed (passes)
Increasing the background opacity or adjusting colors restores sufficient contrast.
<div style="background-color: #cccccc;">
<p style="color: #333333; background-color: rgba(255, 255, 255, 0.85);">
Text with a more opaque background.
</p>
</div>
Using CSS classes (passes)
In practice, you’ll likely use CSS classes rather than inline styles. Ensure your design system tokens and theme colors meet contrast requirements.
<style>
.card {
background-color: #1a1a2e;
}
.card-text {
color: #e0e0e0;
}
</style>
<div class="card">
<p class="card-text">
Light text on a dark background with good contrast.
</p>
</div>
When a web page uses CSS media queries like @media (orientation: portrait) or @media (orientation: landscape) to force content into a single orientation, it prevents the page from responding to the user’s actual device position. This is checked by the axe rule css-orientation-lock.
Why this matters
Many users physically cannot rotate their devices. People with mobility impairments may have their phone or tablet mounted to a wheelchair, bed, or desk in a fixed orientation. Users with low vision may prefer landscape mode to enlarge text, while others may find portrait easier to read. Locking orientation removes their ability to choose what works best for them.
Beyond motor and vision disabilities, orientation locking also affects users with cognitive disabilities and dyslexia who may rely on a particular layout for readability. Sighted keyboard users who use external displays or stands may also be impacted.
This rule relates to WCAG 2.1 Success Criterion 1.3.4: Orientation (Level AA), which requires that content not restrict its view and operation to a single display orientation unless a specific orientation is essential. Essential use cases are rare — examples include a piano keyboard app, a bank check deposit interface, or a presentation slide display where the orientation is integral to the functionality.
How to fix it
-
Remove orientation-locking CSS. Look for
@mediaqueries that use theorientationfeature combined with styles that effectively hide or completely rearrange content for only one orientation (e.g., settingdisplay: noneorwidth: 0on the body or main content). -
Use responsive design instead. Rather than checking orientation, use
min-widthormax-widthmedia queries to adapt your layout to available space. This naturally accommodates both orientations. - Test in both orientations. Rotate your device or use browser developer tools to simulate both portrait and landscape modes. Verify that all content remains visible and functional.
- Only lock orientation when essential. If your application genuinely requires a specific orientation for core functionality (not just aesthetic preference), document the rationale. This is the only valid exception.
Examples
Incorrect: Locking content to portrait only
This CSS hides the main content area when the device is in landscape orientation, effectively forcing users to use portrait mode:
<style>
@media (orientation: landscape) {
.content {
display: none;
}
.rotate-message {
display: block;
}
}
@media (orientation: portrait) {
.rotate-message {
display: none;
}
}
</style>
<div class="content">
<h1>Welcome to our site</h1>
<p>This content is only visible in portrait mode.</p>
</div>
<div class="rotate-message">
<p>Please rotate your device to portrait mode.</p>
</div>
Incorrect: Using transform to force portrait layout in landscape
<style>
@media (orientation: landscape) {
body {
transform: rotate(-90deg);
transform-origin: top left;
width: 100vh;
height: 100vw;
overflow: hidden;
position: absolute;
}
}
</style>
This forcibly rotates the entire page, fighting against the user’s chosen orientation and creating a confusing, inaccessible experience.
Correct: Responsive layout that works in both orientations
<style>
.content {
padding: 1rem;
}
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
@media (min-width: 600px) {
.grid {
grid-template-columns: 1fr 1fr;
}
}
</style>
<div class="content">
<h1>Welcome to our site</h1>
<div class="grid">
<div>
<p>Column one content.</p>
</div>
<div>
<p>Column two content.</p>
</div>
</div>
</div>
This approach uses min-width instead of orientation, adapting the layout based on available space. The content remains fully accessible and readable whether the device is held in portrait or landscape.
Correct: Using orientation queries for minor style adjustments (not locking)
<style>
.hero-image {
width: 100%;
height: 200px;
object-fit: cover;
}
@media (orientation: landscape) {
.hero-image {
height: 300px;
}
}
</style>
<img class="hero-image" src="hero.jpg" alt="A scenic mountain landscape">
Using orientation media queries is acceptable when you’re making minor visual adjustments without hiding or restricting access to content. The key is that all content and functionality remain available in both orientations.
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 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 an interactive element displays visible text — like a button saying “Submit” — users naturally expect that text to be the element’s name. However, developers sometimes use aria-label or aria-labelledby to set an accessible name that differs from the visible text. This creates a disconnect: what sighted users see and what assistive technologies announce become two different things.
This is a serious accessibility problem that primarily affects speech input users. These users interact with web pages by speaking the names of controls they see on screen. If someone sees a link labeled “Next Page” and says “click Next Page,” but the accessible name is actually “OK,” the speech command fails. The user has no way to know the correct programmatic name, leading to frustration and an inability to use the interface.
This issue also affects screen reader users and users with cognitive disabilities. When a screen reader announces a name that doesn’t match the visible label, it creates confusion — the user may not be sure they’re focused on the right element. For users with cognitive disabilities who rely on consistent, predictable interfaces, this mismatch adds unnecessary complexity.
This rule checks compliance with WCAG 2.1 Success Criterion 2.5.3: Label in Name (Level A). This criterion requires that when a user interface component has a visible text label, the accessible name must contain that visible text. The intent is to ensure that the words users see on screen can be used to interact with the component, regardless of input method.
The rule applies to elements that meet all three conditions:
-
The element has a widget role that supports name from content — specifically:
button,checkbox,gridcell,link,menuitem,menuitemcheckbox,menuitemradio,option,radio,searchbox,switch,tab, ortreeitem. - The element has visible text content.
-
The element has an
aria-labeloraria-labelledbyattribute that overrides the default accessible name.
When evaluating the match, leading and trailing whitespace is ignored, and the comparison is case-insensitive. The complete visible text must either match the accessible name exactly or be fully contained within it.
How to Fix
To resolve this issue:
-
Make the accessible name include the full visible text. If the element’s visible text is “Next Page,” the
aria-labelmust contain “Next Page” — not just part of it, and not something completely different. - Start the accessible name with the visible text. While not strictly required, it’s best practice. This helps speech input users who may only speak the beginning of a label.
-
Consider removing the
aria-labelentirely. If the visible text alone is a sufficient accessible name, you may not need an override at all. The simplest fix is often to let the element derive its name from its content naturally.
Examples
Failing: Accessible name doesn’t match visible text
The visible text says “Next” but the accessible name is “OK”:
<div role="link" aria-label="OK">Next</div>
Failing: Accessible name only contains part of the visible text
The visible text is “The full label” but the accessible name is “the full,” which doesn’t include the complete visible text:
<button aria-label="the full">The full label</button>
Passing: Accessible name matches visible text
The aria-label matches the visible text (trailing whitespace and case differences are ignored):
<div role="link" aria-label="Next Page">next page</div>
Passing: Accessible name contains the full visible text
The visible text “Next Page” is fully contained within the accessible name:
<button aria-label="Next Page in the list">Next Page</button>
Passing: No aria-label override needed
When the visible text is already a good accessible name, simply omit the aria-label:
<button>Next Page</button>
Passing: aria-labelledby references text that includes the visible content
<span id="btn-label">Search the full catalog</span>
<button aria-labelledby="btn-label">Search</button>
Here, the visible text “Search” is contained within the accessible name “Search the full catalog,” so the rule passes. The accessible name begins with the visible label, which is ideal for speech input users.
Every form element — such as text inputs, checkboxes, radio buttons, and select menus — must have a programmatically associated label so that assistive technologies can identify and announce the purpose of each field. Without these labels, screen reader users cannot determine what information is expected, and users with motor impairments lose the benefit of a larger clickable target area that a properly associated <label> provides.
Why This Matters
When a form element lacks a programmatic label, screen readers either announce it generically (e.g., “edit text” or “checkbox”) or skip meaningful context entirely. This leaves blind, low-vision, and deafblind users unable to understand what data a field expects or what option a checkbox represents. They must guess based on surrounding content, which is unreliable and error-prone.
Labels also benefit users with motor impairments. When a <label> element is properly associated with an input, clicking the label text activates or focuses the associated control. This creates a larger click target, which is especially helpful for people with limited dexterity.
Sighted users often rely on visual proximity to infer a field’s purpose, but assistive technologies need an explicit programmatic relationship between the label text and the form control to convey the same information.
Relevant Standards
This rule maps to WCAG 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. It applies across WCAG 2.0, 2.1, and 2.2, as well as Section 508 (§1194.22(n)), EN 301 549 (9.4.1.2), and Trusted Tester guidelines. The user impact is considered critical.
How to Fix It
There are several ways to associate a label with a form element. Use the approach that best fits your situation, listed here from most recommended to least recommended.
1. Explicit <label> with for and id (Recommended)
The most common and reliable method is to use a <label> element whose for attribute matches the id of the form control. This creates an unambiguous programmatic association.
2. Implicit <label> (Wrapping)
Wrap the form control inside a <label> element. The association is implied by the parent-child relationship.
3. aria-label
Use aria-label when the field’s purpose is conveyed visually through an icon or layout rather than visible text. This creates an invisible label that only screen readers announce.
4. aria-labelledby
Use aria-labelledby when the label text already exists elsewhere on the page, or when you need to combine multiple pieces of text into a single label. Reference one or more element id values.
5. placeholder (Not Recommended)
A placeholder attribute can technically provide an accessible name, but it disappears once the user begins typing, removing the visible label. This creates usability problems for everyone and is not a recommended approach.
General Tips
-
Ensure all
idvalues are unique on the page. - Make label text descriptive and meaningful when read aloud in isolation.
-
Remember that buttons (
<button>,<input type="submit">, etc.) are self-labeling through their text content orvalueattribute and do not need a separate<label>. -
Hidden inputs (
<input type="hidden">) do not need labels since they are not presented to users.
Examples
Incorrect: Input without a label
<div>First name:</div>
<input type="text" id="firstname">
The <div> text is visually near the input, but there is no programmatic relationship. A screen reader will announce only “edit text” with no context.
Correct: Explicit label with for and id
<label for="firstname">First name:</label>
<input type="text" id="firstname">
Correct: Implicit label by wrapping
<label>
First name:
<input type="text">
</label>
Correct: aria-label for visually implied fields
<input type="text" aria-label="Search">
<button type="submit">🔍</button>
Correct: aria-labelledby referencing existing text
<div id="temp-label">Temperature</div>
<div id="high-label">High:</div>
<div id="low-label">Low:</div>
<input type="text" aria-labelledby="temp-label high-label">
<input type="text" aria-labelledby="temp-label low-label">
The first input is announced as “Temperature High:” and the second as “Temperature Low:”, combining the referenced text in order.
Incorrect: Relying only on placeholder
<input type="text" placeholder="Enter your email">
While this technically provides an accessible name, the visible hint disappears when the user starts typing, making it difficult to verify the field’s purpose. Always prefer a persistent visible label.
Correct: Visible label with supplementary placeholder
<label for="email">Email address</label>
<input type="text" id="email" placeholder="name@example.com">
Incorrect: Checkbox without a label
<input type="checkbox" id="terms">
I agree to the terms and conditions
The text is adjacent but not associated with the checkbox.
Correct: Labeled checkbox
<input type="checkbox" id="terms">
<label for="terms">I agree to the terms and conditions</label>
Why This Matters
Many users cannot perceive color differences reliably. Approximately 8% of men and 0.4% of women have some form of color vision deficiency, and there are nearly three times more people with low vision than those who are totally blind. For these users, if a link within a paragraph is styled only with a different color — say, blue text in a block of black text — it can be completely invisible as a link.
People with low vision often experience reduced contrast sensitivity, meaning everything appears roughly the same brightness. Without a non-color cue like an underline or a sufficient luminance difference, these users cannot detect that certain text is interactive. This can cause them to miss important navigation, actions, or information entirely.
This rule relates to WCAG 2.0/2.1/2.2 Success Criterion 1.4.1: Use of Color (Level A), which requires that color is not used as the only visual means of conveying information, indicating an action, or distinguishing a visual element. Links embedded in text are one of the most common places where this requirement is violated.
How the Rule Works
The axe rule link-in-text-block checks links that appear inside blocks of text (such as paragraphs) and evaluates them in three steps:
-
Non-color distinction present → Pass. If the link has a visual style that doesn’t depend on color — such as an underline, a border, a background color, or a distinct font weight/style — the rule passes automatically.
-
No non-color distinction and contrast below 3:1 → Fail. If the link relies only on color and the contrast ratio between the link text color and the surrounding text color is less than 3:1, the rule fails.
-
No non-color distinction but contrast is 3:1 or higher → Needs Review. If the link relies only on color but meets the 3:1 contrast threshold, the rule flags it for manual testing. You must verify that the link receives a distinct visual style (such as an underline) on
:focusand:hoverstates.
How to Fix It
The simplest and most reliable fix is to give links a non-color visual indicator. Here are your options, in order of recommendation:
- Underline the link — This is the most universally understood link indicator.
-
Add a border — A
border-bottomcan work as an alternative totext-decoration. - Use a distinct font style — Bold or italic can help, though underline is more conventional for links.
- Add a background or outline — A subtle background color difference can work if it’s clearly visible.
If you choose to rely on color contrast alone (3:1 minimum between link text and surrounding text), you must also ensure the link gains a distinct non-color style on :hover and :focus. This two-part requirement exists because static contrast alone may not be sufficient for all users, but a visual change on interaction confirms the element is interactive.
Examples
Incorrect: Link distinguished only by color with insufficient contrast
<style>
p { color: #333333; }
a { color: #555555; text-decoration: none; }
</style>
<p>
Learn more about our
<a href="/services">consulting services</a>
and how we can help.
</p>
In this example, the link has no underline and the color contrast between #555555 and #333333 is well below 3:1. Users with low vision or color blindness cannot identify the link.
Correct: Link has an underline (recommended)
<style>
p { color: #333333; }
a { color: #0056b3; text-decoration: underline; }
</style>
<p>
Learn more about our
<a href="/services">consulting services</a>
and how we can help.
</p>
The underline provides a clear non-color visual cue, making the link identifiable regardless of color perception.
Correct: Link uses a bottom border instead of underline
<style>
p { color: #333333; }
a {
color: #0056b3;
text-decoration: none;
border-bottom: 2px solid #0056b3;
}
</style>
<p>
Read our
<a href="/guide">accessibility guide</a>
for detailed instructions.
</p>
Correct: Color-only link with sufficient contrast plus hover/focus styles
<style>
p { color: #333333; }
a {
color: #0000ee;
text-decoration: none;
}
a:hover,
a:focus {
text-decoration: underline;
}
</style>
<p>
Visit our
<a href="/help">help center</a>
for answers to common questions.
</p>
Here the contrast between #0000ee and #333333 exceeds 3:1, and the link gains an underline on hover and focus. This satisfies the requirement, though note that axe will still flag this for manual review since it cannot automatically verify the hover/focus styles in all cases.
Incorrect: Underline removed with no replacement
<style>
a { color: #1a73e8; text-decoration: none; }
p { color: #000000; }
</style>
<p>
Check out our
<a href="/blog">latest blog posts</a>
for updates.
</p>
Even though the blue color may seem obvious to sighted users with full color vision, removing the underline without providing another non-color indicator makes this link invisible to users with color blindness or low contrast sensitivity.
The <marquee> element was never part of any official HTML standard and has been deprecated by all major browsers. It produces text that continuously scrolls across the screen, making it extremely difficult to read, interact with, or comprehend. Even though browsers may still render it, the element should never appear in modern web content.
Why This Is an Accessibility Problem
Scrolling marquee text creates barriers for several groups of users:
- Users with low vision may not be able to read text that is constantly in motion. The movement makes it nearly impossible to track and process the words.
- Users with cognitive disabilities or attention deficits can be distracted or overwhelmed by content that moves without their control. Automatic motion competes for attention and can make it difficult to focus on other parts of the page.
- Users with limited motor skills may be unable to accurately click on links or interactive elements embedded within scrolling content, since the targets are constantly shifting position.
-
Screen reader users may encounter inconsistent or confusing output, since the
<marquee>element is non-standard and assistive technologies are not required to support it.
This rule relates to WCAG 2.2 Success Criterion 2.2.2: Pause, Stop, Hide (Level A), which requires that for any moving, blinking, or scrolling content that starts automatically and lasts more than five seconds, there must be a mechanism for users to pause, stop, or hide it. The <marquee> element provides no such mechanism by default, making it a failure of this criterion. This is also documented as a known failure technique: F16: Failure of Success Criterion 2.2.2 due to including scrolling content where movement is not essential to the activity without also including a mechanism to pause and restart the content.
How to Fix It
The fix is straightforward: remove all <marquee> elements from your HTML, including empty ones. Then decide how to handle the content:
-
Display the content as static text. In most cases, this is the best approach. Simply place the text in a standard element like a
<p>or<span>. -
If motion is truly needed, use CSS animations instead, and provide a visible control (such as a pause button) that allows users to stop the animation. Ensure the animation also respects the
prefers-reduced-motionmedia query.
Examples
Incorrect: Using the <marquee> Element
This code uses the deprecated <marquee> element to create scrolling text with links. Users cannot pause or stop the movement.
<marquee behavior="scroll" direction="left">
Frisbeetarianism is the
<a href="https://en.wikipedia.org/wiki/Belief">belief</a> that when you
<a href="https://en.wikipedia.org/wiki/Death">die</a>, your
<a href="https://en.wikipedia.org/wiki/Soul">soul</a> goes up on the
<a href="https://en.wikipedia.org/wiki/Roof">roof</a> and gets stuck.
</marquee>
Correct: Static Text
The simplest and most accessible fix is to display the content as regular, non-moving text.
<p>
Frisbeetarianism is the
<a href="https://en.wikipedia.org/wiki/Belief">belief</a> that when you
<a href="https://en.wikipedia.org/wiki/Death">die</a>, your
<a href="https://en.wikipedia.org/wiki/Soul">soul</a> goes up on the
<a href="https://en.wikipedia.org/wiki/Roof">roof</a> and gets stuck.
</p>
Correct: CSS Animation with Pause Control and Reduced Motion Support
If scrolling text is a design requirement, use CSS with a pause button and respect the user’s motion preferences.
<div class="scrolling-container">
<p class="scrolling-text" id="ticker">
Breaking news: Accessibility makes the web better for everyone.
</p>
<button type="button" onclick="document.getElementById('ticker').classList.toggle('paused')">
Pause / Resume
</button>
</div>
<style>
.scrolling-text {
animation: scroll-left 10s linear infinite;
}
.scrolling-text.paused {
animation-play-state: paused;
}
@keyframes scroll-left {
from { transform: translateX(100%); }
to { transform: translateX(-100%); }
}
@media (prefers-reduced-motion: reduce) {
.scrolling-text {
animation: none;
}
}
</style>
This approach gives users control over the motion and automatically disables the animation for users who have indicated they prefer reduced motion in their operating system settings.
The <meta name="viewport"> element controls how a page is displayed on mobile and responsive layouts. Two of its parameters — user-scalable and maximum-scale — directly affect whether users can zoom in on page content. Setting user-scalable="no" completely disables pinch-to-zoom and browser zoom controls, while a low maximum-scale value (e.g., 1 or 2) caps how far a user can zoom in.
Why This Matters
People with low vision frequently rely on zoom and screen magnification to read web content. When zooming is disabled or restricted, these users may be unable to perceive text, images, or interactive elements at all. This creates a significant barrier to accessing information.
WCAG Success Criterion 1.4.4 (Resize Text) requires that text can be resized up to 200% without loss of content or functionality. However, as a best practice recommended by Deque, pages should support zooming up to 500% (5x). This higher threshold better accommodates users who need substantial magnification and aligns with the principle of providing the widest possible range of accessibility.
This rule is classified as a Deque Best Practice and primarily affects users with low vision. While it goes beyond the strict WCAG 200% requirement, supporting 5x zoom is a meaningful improvement that costs nothing to implement.
How to Fix It
-
Remove
user-scalable="no"from thecontentattribute of the<meta name="viewport">element. If present, either remove it entirely or set it toyes. -
Ensure
maximum-scaleis at least5(representing 500% zoom). If you don’t need to cap zoom at all, simply omit themaximum-scaleparameter — browsers will allow unrestricted zooming by default.
Examples
Incorrect: Zooming Disabled
This viewport meta tag prevents users from zooming at all:
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
Incorrect: Zoom Restricted Below 500%
This viewport meta tag limits zooming to 200%, which falls short of the recommended 500%:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
Correct: Zooming Allowed Up to 500%
This viewport meta tag explicitly permits zooming and sets the maximum scale to 5:
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5">
Correct: No Zoom Restrictions
The simplest and most accessible approach is to omit both user-scalable and maximum-scale entirely, allowing the browser to handle zoom without limits:
<meta name="viewport" content="width=device-width, initial-scale=1">
Correct: Explicitly Enabling User Scaling
You can also explicitly set user-scalable=yes for clarity, though this is the default behavior:
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes, maximum-scale=5">
Key Points
- The browser viewport (the visible area of the page) is separate from programmatic focus. When a user zooms in, only a portion of the page is visible at a time, but focus does not automatically follow the viewport. This is expected behavior — users scroll and pan to find content.
- Removing zoom restrictions does not break layouts when responsive design practices are followed. Modern CSS and flexible layouts adapt naturally to zoom.
-
This rule checks that
user-scalable="no"is not present in the<meta name="viewport">element, and thatmaximum-scaleis not less than 5 (500%).
The <meta name="viewport"> element controls how a page is displayed on mobile devices, including its dimensions and scale. Two parameters within its content attribute can restrict zooming:
-
user-scalable="no"— Completely disables pinch-to-zoom and other user-initiated scaling on the page. -
maximum-scaleset below 2 — Caps how far a user can zoom in, preventing them from reaching the 200% zoom level required by WCAG.
Both of these restrictions create serious barriers for people with low vision. Many users depend on zooming to enlarge text and interface elements to a readable size. When zooming is disabled or limited, these users may be unable to use the page at all. Screen magnification tools and native browser zoom are fundamental assistive strategies, and restricting them undermines the accessibility of the entire page.
This rule relates to WCAG 2.0/2.1/2.2 Success Criterion 1.4.4: Resize Text (Level AA), which requires that text can be resized up to 200% without loss of content or functionality. By blocking zoom below that threshold, you fail this criterion. This success criterion exists because the ability to enlarge content is one of the most basic and widely used accessibility features across all platforms.
How to Fix
-
Remove
user-scalable="no"from thecontentattribute of your<meta name="viewport">element. If present, either delete it or set it touser-scalable="yes". -
Remove or increase
maximum-scale. If you usemaximum-scale, set it to at least2(which allows 200% zoom). Better yet, remove it entirely to allow unrestricted zooming. - Test on mobile devices after making changes. Verify that pinch-to-zoom works and that content remains usable when zoomed to 200%.
Examples
Incorrect: Zooming is completely disabled
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
The user-scalable=no parameter prevents users from zooming in at all.
Incorrect: Maximum scale is too restrictive
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.5">
Setting maximum-scale to 1.5 limits zoom to 150%, which is below the required 200% threshold.
Correct: Zooming is fully allowed
<meta name="viewport" content="width=device-width, initial-scale=1">
By omitting both user-scalable and maximum-scale, the browser’s default zoom behavior is preserved and users can zoom freely.
Correct: Explicitly allowing zoom with a sufficient maximum scale
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes, maximum-scale=5">
Here, user-scalable=yes explicitly permits zooming, and maximum-scale=5 allows up to 500% zoom, well above the 200% minimum.
Correct: Setting maximum-scale to exactly 2
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
A maximum-scale of 2 allows 200% zoom, meeting the minimum WCAG requirement. Allowing higher values is even better, but 2 is the minimum acceptable value.
What This Rule Checks
The axe rule meta-viewport inspects the <meta name="viewport"> element in your document’s <head> and verifies two things:
-
The
user-scalableparameter is not set tono(or0). -
The
maximum-scaleparameter, if present, is not less than2.
If either condition is violated, the rule flags the page as having a zoom restriction that may prevent users with low vision from accessing content.
Screen reader users rely heavily on heading navigation to move efficiently through web pages. Most screen readers provide keyboard shortcuts (such as pressing H to jump to the next heading, or 1 to jump to the next h1) that let users skip directly to the main content. When a page lacks a level-one heading, these shortcuts fail, and users are forced to listen through navigation menus, banners, and other content before reaching what they came for.
This is fundamentally a problem of perceivability and navigability. Sighted users can glance at a page and instantly understand its layout — they see the large, bold title and know where the main content begins. Blind, low-vision, and deafblind users don’t have that option. For them, headings serve as a structural outline of the page. A well-organized heading hierarchy starting with an h1 gives these users an equivalent way to quickly grasp the page’s structure and jump to the content they need.
This rule is a Deque best practice and aligns with broader accessibility principles in WCAG, including Success Criterion 1.3.1 (Info and Relationships), which requires that structural information conveyed visually is also available programmatically, and Success Criterion 2.4.6 (Headings and Labels), which requires headings to be descriptive. While not a strict WCAG failure, the absence of an h1 has a moderate impact on usability for assistive technology users.
How to Fix It
-
Add a single
h1element at the beginning of your page’s main content. This heading should describe the primary topic or purpose of the page. -
Use only one
h1per page. While HTML5 technically allows multipleh1elements, best practice is to use a singleh1that represents the top-level topic. -
Build a logical heading hierarchy. After the
h1, useh2for major sections,h3for subsections within those, and so on. Don’t skip heading levels (e.g., jumping fromh1toh4). -
Handle iframes carefully. If your page includes an
iframe, the heading hierarchy inside that iframe should fit within the parent page’s heading structure. If the parent page already has anh1, the iframe content should start withh2or lower, depending on where it sits in the hierarchy. When the iframe contains third-party content you can’t control, this may not always be possible, but it’s still the ideal approach.
What the Rule Checks
This rule verifies that the page (or at least one of its frames) contains at least one element matching either h1:not([role]) or [role="heading"][aria-level="1"].
Examples
Incorrect: Page with No Level-One Heading
This page jumps straight to h2 headings without ever establishing an h1. Screen reader users have no top-level heading to navigate to.
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<main>
<h2>Welcome to Our Store</h2>
<p>Browse our latest products below.</p>
<h2>Featured Products</h2>
<p>Check out what's new this week.</p>
</main>
</body>
Correct: Page with a Level-One Heading
The h1 clearly identifies the page’s main topic and appears at the start of the main content. Subsections use h2.
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<main>
<h1>Welcome to Our Store</h1>
<p>Browse our latest products below.</p>
<h2>Featured Products</h2>
<p>Check out what's new this week.</p>
</main>
</body>
Correct: Using ARIA to Create a Level-One Heading
If you can’t use a native h1 element, you can use the role="heading" and aria-level="1" attributes on another element. Native HTML headings are always preferred, but this approach is valid.
<main>
<div role="heading" aria-level="1">Welcome to Our Store</div>
<p>Browse our latest products below.</p>
</main>
Correct: Page with an Iframe That Fits the Heading Hierarchy
When embedding content in an iframe, the iframe’s heading structure should continue from the parent page’s hierarchy rather than introducing a second h1.
<!-- Parent page -->
<body>
<main>
<h1>Company Dashboard</h1>
<h2>Recent Activity</h2>
<iframe src="activity-feed.html" title="Activity feed"></iframe>
</main>
</body>
<!-- activity-feed.html -->
<body>
<h3>Today's Updates</h3>
<p>Three new items were added.</p>
</body>
The role="none" and role="presentation" attributes tell browsers to strip the semantic meaning from an element, effectively removing it from the accessibility tree. This is useful when elements are used purely for visual layout and carry no meaningful content for assistive technology users.
However, the WAI-ARIA specification defines specific conditions under which this removal is overridden. If a presentational element has a global ARIA attribute (such as aria-hidden, aria-label, aria-live, aria-describedby, etc.) or is focusable (either natively, like a <button>, or through tabindex), the browser must ignore the presentational role and keep the element in the accessibility tree. This is known as a presentational role conflict.
When this conflict occurs, screen reader users may encounter elements that were intended to be hidden but are instead announced — potentially with confusing or missing context. This creates a disorienting experience, particularly for blind users and users with low vision who rely on screen readers to understand the page structure.
This rule is flagged as a Deque best practice. While not mapped to a specific WCAG success criterion, it supports the broader goal of ensuring the accessibility tree accurately represents the author’s intent, which contributes to a coherent experience under WCAG principles like 1.3.1 Info and Relationships and 4.1.2 Name, Role, Value.
How to Fix It
For every element with role="none" or role="presentation", ensure that:
-
No global ARIA attributes are present. Remove attributes like
aria-hidden,aria-label,aria-live,aria-describedby,aria-atomic, and any other global ARIA attributes. -
The element is not focusable. Don’t use natively focusable elements (like
<button>,<a href>, or<input>) with a presentational role. Also, don’t addtabindexto a presentational element.
If the element genuinely needs a global ARIA attribute or needs to be focusable, then it shouldn’t have a presentational role — remove role="none" or role="presentation" instead.
Examples
Incorrect: Presentational element with a global ARIA attribute
The aria-hidden="true" attribute is a global ARIA attribute, which creates a conflict with role="none":
<li role="none" aria-hidden="true">Decorative item</li>
Incorrect: Natively focusable element with a presentational role
A <button> is natively focusable, so its presentational role will be ignored by the browser:
<button role="none">Click me</button>
Incorrect: Presentational element made focusable via tabindex
Adding tabindex="0" makes the element focusable, overriding the presentational role:
<img alt="" role="presentation" tabindex="0">
Correct: Presentational element with no conflicts
These elements have no global ARIA attributes and are not focusable, so they will be properly removed from the accessibility tree:
<li role="none">Layout item</li>
<li role="presentation">Layout item</li>
<img alt="" role="presentation">
Correct: Removing the presentational role when focus is needed
If the element needs to be focusable, remove the presentational role and give it an appropriate accessible name instead:
<button aria-label="Submit form">Submit</button>
When a <select> element lacks an accessible name, screen readers announce it as something generic like “combobox” or “listbox” with no context about what it represents. A sighted user might see nearby text like “Country” and understand the purpose, but that visual proximity means nothing to assistive technology unless a programmatic relationship exists between the text and the form control.
This issue critically affects users who are blind, have low vision, or have mobility impairments and rely on assistive technologies to interact with forms. Without a proper label, these users cannot determine what data a dropdown expects, making form completion error-prone or impossible.
Related WCAG Success Criteria
This rule maps to WCAG 2.0, 2.1, and 2.2 Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that all user interface components have a name that can be programmatically determined. It also falls under Section 508 §1194.22(n), which mandates that online forms allow people using assistive technology to access all field elements, directions, and cues needed for completion.
How to Fix It
There are several ways to give a <select> element an accessible name, listed here from most preferred to least preferred:
-
Explicit
<label>withfor/id— The most common and recommended approach. Use theforattribute on the<label>to match theidof the<select>. This also gives sighted users a larger click target. -
Implicit
<label>wrapping — Wrap the<select>inside a<label>element. Nofor/idpairing is needed. -
aria-labelledby— Point to theidof an existing visible text element that serves as the label. Useful when a traditional<label>would break layout or when multiple elements combine to form the label. -
aria-label— Provide an invisible text label directly on the<select>. Use this only when no visible label text exists or is appropriate.
Whichever method you choose, make sure:
- The label text clearly describes what the user should select.
-
Each
idattribute is unique on the page. -
Each
<select>has only one labeling method to avoid conflicts or confusion.
Examples
Incorrect: No Label Association
This places text near the <select> but creates no programmatic link between them. Screen readers will not announce “State” when the user focuses the dropdown.
State:
<select>
<option value="ny">New York</option>
<option value="ca">California</option>
</select>
Correct: Explicit <label> with for and id
The for attribute on the <label> matches the id on the <select>, creating a clear programmatic association.
<label for="state">State:</label>
<select id="state">
<option value="ny">New York</option>
<option value="ca">California</option>
</select>
Correct: Implicit <label> Wrapping
Wrapping the <select> inside the <label> element implicitly associates them.
<label>
State:
<select>
<option value="ny">New York</option>
<option value="ca">California</option>
</select>
</label>
Correct: Using aria-labelledby
When visible text already exists elsewhere (e.g., in a heading or table cell), use aria-labelledby to reference it by id.
<span id="state-label">State:</span>
<select aria-labelledby="state-label">
<option value="ny">New York</option>
<option value="ca">California</option>
</select>
Correct: Using aria-label
When no visible label is present or appropriate, aria-label provides an accessible name directly. Note that this label is invisible to sighted users, so only use it when context is already visually clear.
<select aria-label="State">
<option value="ny">New York</option>
<option value="ca">California</option>
</select>
Ready to validate your sites?
Start your free trial today.