Guías de accesibilidad
Aprende a identificar y solucionar problemas comunes de accesibilidad detectados por Axe Core, para que tus páginas sean inclusivas y utilizables para todos. También consulta nuestras Guías de validación HTML.
When you add an ARIA attribute to an HTML element, the browser exposes that information through the accessibility tree so assistive technologies like screen readers can interpret it. If the attribute name is invalid — whether due to a typo like aria-hiden instead of aria-hidden, or a fabricated attribute like aria-visible that doesn’t exist in the spec — the browser won’t recognize it. The attribute is effectively dead code, and the accessibility enhancement you intended never reaches the user.
This is classified as a critical issue because the consequences can be severe. For example, if you misspell aria-required as aria-requried on a form field, screen reader users won’t be informed that the field is mandatory. If you misspell aria-expanded on a disclosure widget, blind and deafblind users won’t know whether a section is open or closed. Keyboard-only users who rely on screen readers are also affected when interactive states and properties fail to communicate correctly.
Related WCAG Success Criteria
This rule maps to WCAG Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that for all user interface components, the name, role, and states/properties can be programmatically determined. Invalid ARIA attributes fail to communicate states and properties to assistive technologies, directly violating this criterion. This applies across WCAG 2.0, 2.1, and 2.2, as well as EN 301 549 (guideline 9.4.1.2).
How to Fix It
-
Audit your ARIA attributes. Review every attribute in your markup that starts with
aria-and confirm it matches a valid attribute name from the WAI-ARIA specification. -
Check for typos. Common mistakes include
aria-labelled-by(correct:aria-labelledby),aria-hiden(correct:aria-hidden), andaria-discribedby(correct:aria-describedby). -
Remove invented attributes. Attributes like
aria-visible,aria-tooltip, oraria-icondo not exist in the WAI-ARIA spec and will have no effect. -
Use tooling. IDE extensions, linters (like
eslint-plugin-jsx-a11y), and the axe DevTools browser extension can catch invalid ARIA attribute names during development.
Common Valid ARIA Attributes
Here are some frequently used ARIA attributes for reference:
-
Widget attributes:
aria-checked,aria-disabled,aria-expanded,aria-hidden,aria-label,aria-pressed,aria-readonly,aria-required,aria-selected,aria-valuenow -
Live region attributes:
aria-live,aria-atomic,aria-relevant,aria-busy -
Relationship attributes:
aria-labelledby,aria-describedby,aria-controls,aria-owns,aria-flowto -
Drag-and-drop attributes:
aria-dropeffect,aria-grabbed
Examples
Incorrect: Misspelled ARIA Attribute
<button aria-expandd="false">Show details</button>
The attribute aria-expandd is not a valid ARIA attribute. Screen readers will not announce the expanded/collapsed state of this button.
Incorrect: Non-Existent ARIA Attribute
<div aria-visible="true">Important announcement</div>
The attribute aria-visible does not exist in the WAI-ARIA specification. It will be completely ignored by assistive technologies.
Incorrect: Typo in a Relationship Attribute
<input type="text" aria-discribedby="help-text">
<p id="help-text">Enter your full name as it appears on your ID.</p>
The attribute aria-discribedby is a misspelling of aria-describedby. The input will not be associated with the help text for screen reader users.
Correct: Properly Spelled ARIA Attributes
<button aria-expanded="false">Show details</button>
<div aria-hidden="true">Decorative content</div>
<input type="text" aria-describedby="help-text">
<p id="help-text">Enter your full name as it appears on your ID.</p>
Each of these examples uses a valid, correctly spelled ARIA attribute that browsers and assistive technologies will recognize and process as intended.
When an <audio> element lacks a captions track, all of the information it conveys — dialogue, narration, sound effects, musical cues, and speaker identification — becomes completely inaccessible to users who are deaf or deafblind. This is considered a critical accessibility issue because it blocks entire groups of users from accessing content.
This rule relates to WCAG Success Criterion 1.2.1: Audio-only and Video-only (Prerecorded) (Level A), which requires that a text alternative be provided for prerecorded audio-only content. It also falls under Section 508 requirements and EN 301 549. Level A criteria represent the most fundamental accessibility requirements — failing to meet them means significant barriers exist for users with disabilities.
Captions vs. Subtitles
It’s important to understand that captions and subtitles are not the same thing:
-
Captions (
kind="captions") are designed for deaf and hard-of-hearing users. They include dialogue, speaker identification, sound effects (e.g., “[door slams]”), musical cues (e.g., “[soft piano music]”), and other meaningful audio information. -
Subtitles (
kind="subtitles") are language translations intended for hearing users who don’t understand the spoken language. They typically include only dialogue and narration.
Because of this distinction, you must use kind="captions", not kind="subtitles", to satisfy this rule.
How to Fix It
-
Create a captions file (typically in WebVTT
.vttformat) that includes all meaningful audio information: who is speaking, what they say, and relevant non-speech sounds. -
Add a
<track>element inside your<audio>element. -
Set the
kindattribute to"captions". -
Set the
srcattribute to the path of your captions file. -
Use the
srclangattribute to specify the language of the captions. -
Use the
labelattribute to give the track a human-readable name.
While only src is technically required on a <track> element, including kind, srclang, and label is strongly recommended for clarity and proper functionality.
Examples
Incorrect: <audio> with no captions track
<audio controls>
<source src="podcast.mp3" type="audio/mp3">
</audio>
This fails the rule because there is no <track> element providing captions.
Incorrect: <track> with wrong kind value
<audio controls>
<source src="podcast.mp3" type="audio/mp3">
<track src="subs_en.vtt" kind="subtitles" srclang="en" label="English">
</audio>
This fails because kind="subtitles" does not satisfy the captions requirement. Subtitles are not a substitute for captions.
Correct: <audio> with a captions track
<audio controls>
<source src="podcast.mp3" type="audio/mp3">
<track src="captions_en.vtt" kind="captions" srclang="en" label="English Captions">
</audio>
Correct: <audio> with multiple caption tracks for different languages
<audio controls>
<source src="interview.mp3" type="audio/mp3">
<track src="captions_en.vtt" kind="captions" srclang="en" label="English Captions">
<track src="captions_es.vtt" kind="captions" srclang="es" label="Subtítulos en español">
</audio>
Providing captions in multiple languages ensures broader accessibility and is especially helpful when your audience speaks different languages.
Example WebVTT captions file
A basic captions_en.vtt file might look like this:
WEBVTT
00:00:01.000 --> 00:00:04.000
[Upbeat intro music]
00:00:04.500 --> 00:00:07.000
Host: Welcome to the show, everyone.
00:00:07.500 --> 00:00:10.000
Guest: Thanks for having me!
00:00:10.500 --> 00:00:12.000
[Audience applause]
Notice how the captions include speaker identification (Host:, Guest:), non-speech sounds ([Upbeat intro music], [Audience applause]), and the full dialogue. This level of detail is what makes captions effective for deaf and deafblind users.
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.
When a button lacks an accessible name, assistive technologies like screen readers can only announce it generically — for example, as “button” — with no indication of its purpose. This is a critical barrier for people who are blind or deafblind, as they rely entirely on programmatically determined names to understand and interact with interface controls. A sighted user might infer a button’s purpose from an icon or visual context, but without a text-based name, that information is completely lost to assistive technology users.
This rule maps to WCAG 2.0, 2.1, and 2.2 Success Criterion 4.1.2: Name, Role, Value (Level A), which requires that all user interface components have a name that can be programmatically determined. It is also covered by Section 508, EN 301 549 (9.4.1.2), and Trusted Tester guidelines, which require that the purpose of every link and button be determinable from its accessible name, description, or context.
How to fix it
Ensure every <button> element or element with role="button" has an accessible name through one of these methods:
- Visible text content inside the button element.
-
A non-empty
aria-labelattribute that describes the button’s purpose. -
An
aria-labelledbyattribute that references an element containing visible, non-empty text. -
A
titleattribute (use as a last resort, sincetitletooltips are inconsistently exposed across devices).
If a button is purely decorative and should be hidden from assistive technologies, you can assign role="presentation" or role="none" and remove it from the tab order with tabindex="-1". However, this is rare for interactive buttons.
Common mistakes to avoid
-
Leaving a
<button>element completely empty. -
Using only a
valueattribute on a<button>— unlike<input>elements, thevalueattribute on<button>does not provide an accessible name. -
Setting
aria-labelto an empty string (aria-label=""). -
Pointing
aria-labelledbyto an element that doesn’t exist or contains no text. - Using only an icon or image inside a button without providing alternative text.
Examples
Incorrect: empty button
<button id="search"></button>
A screen reader announces this as “button” with no indication of its purpose.
Incorrect: button with only a value attribute
<button id="submit" value="Submit"></button>
The value attribute does not set the accessible name for <button> elements.
Incorrect: empty aria-label
<button id="close" aria-label=""></button>
An empty aria-label results in no accessible name.
Incorrect: aria-labelledby pointing to a missing or empty element
<button id="save" aria-labelledby="save-label"></button>
<div id="save-label"></div>
The referenced element exists but contains no text, so the button has no accessible name.
Correct: button with visible text
<button>Submit order</button>
Correct: icon button with aria-label
<button aria-label="Close dialog">
<svg aria-hidden="true" focusable="false">
<use href="#icon-close"></use>
</svg>
</button>
The aria-label provides the accessible name, while aria-hidden="true" on the SVG prevents duplicate announcements.
Correct: button labeled by another element
<h2 id="section-title">Shopping cart</h2>
<button aria-labelledby="section-title">
<svg aria-hidden="true" focusable="false">
<use href="#icon-arrow"></use>
</svg>
</button>
The button’s accessible name is drawn from the referenced heading text.
Correct: button with aria-label and visible text
<button aria-label="Search products">Search</button>
When both aria-label and inner text are present, aria-label takes precedence as the accessible name. Use this when you need a more descriptive name than what the visible text alone conveys.
Correct: button with title (last resort)
<button title="Print this page">
<svg aria-hidden="true" focusable="false">
<use href="#icon-print"></use>
</svg>
</button>
The title attribute provides an accessible name, but visible text or aria-label are preferred because title tooltips may not be available to touch-screen or keyboard-only users.
Websites typically repeat navigation links, branding, and other interface elements across every page. While sighted mouse users can visually scan past these blocks and click wherever they want, keyboard-only users and screen reader users must navigate through every interactive element sequentially. Without a bypass mechanism, a keyboard user might need to press Tab dozens of times just to reach the primary content on each new page they visit. For users with severe motor impairments, this can take several minutes per page and cause fatigue or physical pain. Even users with less severe limitations will experience frustrating delays compared to mouse users, who can reach any link in a second or two.
Screen reader users also benefit significantly from bypass mechanisms. Landmarks like <main>, <nav>, and <header> allow screen readers to present a structural outline of the page, enabling users to jump directly to the section they need. Headings serve a similar purpose — screen reader users can navigate by heading level to quickly locate the main content area.
This rule maps to WCAG 2.4.1 Bypass Blocks (Level A), which requires that a mechanism is available to bypass blocks of content repeated on multiple pages. It is also required by Section 508 (specifically §1194.22(o)), the Trusted Tester guidelines, and EN 301 549. Because it is a Level A requirement, it represents the minimum baseline for accessibility compliance.
How the Rule Works
The axe bypass rule checks that a page includes at least one of the following:
-
A landmark region (such as
<main>,<nav>,<header>, or<footer>) -
A heading (an
<h1>through<h6>element) - An internal skip link (an anchor link that points to a location further down the page)
If none of these are present, the rule flags the page as a failure.
How to Fix It
The best approach is to use HTML5 landmark elements to structure your page. At a minimum, include a <main> element that wraps the primary content of the page. You should also use <header>, <nav>, and <footer> to identify other common sections. A page should have only one <main> landmark.
Additionally, consider adding a skip navigation link as the very first focusable element on the page. This provides an immediate shortcut for keyboard users who don’t use screen readers and may not be able to navigate by landmarks.
Prefer native HTML5 elements over their ARIA equivalents. For example, use <main> rather than <div role="main">. Native elements are better supported and require less code.
Examples
Incorrect: No Landmarks, Headings, or Skip Links
This page has no structural landmarks, no headings, and no skip link. Keyboard users must tab through every element to reach the content.
<div class="header">
<div class="logo">My Site</div>
<div class="nav">
<a href="/home">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</div>
</div>
<div class="content">
<p>This is the main content of the page.</p>
</div>
<div class="footer">
<p>Footer information</p>
</div>
Correct: Using HTML5 Landmark Elements
Replacing generic <div> wrappers with semantic HTML5 elements gives the page proper structure that assistive technologies can use for navigation.
<header>
<div class="logo">My Site</div>
<nav>
<a href="/home">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
</header>
<main>
<h1>Welcome</h1>
<p>This is the main content of the page.</p>
<section>
<h2>Latest News</h2>
<p>Section content here.</p>
</section>
</main>
<footer>
<p>Footer information</p>
</footer>
Correct: Adding a Skip Navigation Link
A skip link gives keyboard users an immediate way to bypass repeated content. It is typically visually hidden until it receives focus.
<body>
<a class="skip-link" href="#main-content">Skip to main content</a>
<header>
<nav>
<a href="/home">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
</header>
<main id="main-content">
<h1>Page Title</h1>
<p>This is the main content of the page.</p>
</main>
<footer>
<p>Footer information</p>
</footer>
</body>
.skip-link {
position: absolute;
left: -9999px;
top: auto;
width: 1px;
height: 1px;
overflow: hidden;
}
.skip-link:focus {
position: static;
width: auto;
height: auto;
overflow: visible;
}
When the skip link receives keyboard focus, it becomes visible, and pressing Enter moves focus directly to the <main> element. Combined with proper landmark elements, this gives all users fast, reliable access to the page’s primary content.
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.