Accessibility Guides for WCAG 2.0 (AAA)
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.
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>
When screen reader users navigate a page, they often pull up a list of all links to quickly scan available destinations. If that list contains several links all labeled “Read more,” there’s no way to tell them apart. This creates confusion and forces users to leave the link list, find each link in context, and read the surrounding content to understand its purpose. Users who are blind or deafblind are most affected, but this issue also impacts anyone who relies on link text to understand navigation options.
This rule relates to WCAG Success Criterion 2.4.9: Link Purpose (Link Only), a Level AAA requirement. It states that the purpose of each link should be determinable from the link text alone. While the related criterion 2.4.4 (Level A) allows link purpose to be determined from context, SC 2.4.9 sets a higher bar: each link’s text must be self-explanatory on its own. Additionally, Success Criterion 3.2.4: Consistent Identification requires that components with the same functionality are identified consistently. Together, these criteria mean that links with the same name should go to the same place, and links that go to different places should have different names.
How to Fix the Problem
The key principle is straightforward: if two links have the same accessible name, they should serve the same purpose. If they serve different purposes, give them distinct accessible names.
Here are practical ways to fix this:
- Write descriptive link text. Instead of generic phrases like “Read more” or “Click here,” write link text that describes the destination, such as “Read the accessibility policy” or “View January’s meeting minutes.”
-
Use
aria-labelto differentiate links. When the visible text must be generic (e.g., for design reasons), usearia-labelto provide a unique, descriptive name for each link. -
Use
aria-labelledbyto combine visible headings or other text with the link to form a unique accessible name. -
Provide meaningful
alttext on image links. If a link contains only an image, the image’saltattribute becomes the link’s accessible name. Make sure it describes the link’s destination.
Examples
Incorrect: Multiple links with the same name going to different pages
In this example, two “Read more” links appear identical to assistive technology but lead to entirely different articles:
<h3>Neighborhood News</h3>
<p>Seminole tax hike: City managers propose a 75% increase in property taxes.
<a href="taxhike.html">Read more...</a>
</p>
<p>Baby Mayor: Voters elect the city's youngest mayor ever.
<a href="babymayor.html">Read more...</a>
</p>
A screen reader listing all links on this page would show two identical entries — “Read more…” — with no way to distinguish them.
Correct: Using aria-label to differentiate links
By adding an aria-label, each link gets a unique accessible name while keeping the visible text short:
<h3>Neighborhood News</h3>
<p>Seminole tax hike: City managers propose a 75% increase in property taxes.
<a href="taxhike.html" aria-label="Read more about Seminole tax hike">Read more...</a>
</p>
<p>Baby Mayor: Voters elect the city's youngest mayor ever.
<a href="babymayor.html" aria-label="Read more about Seminole's new baby mayor">Read more...</a>
</p>
Correct: Writing descriptive link text directly
The simplest approach is to make the link text itself descriptive:
<a href="routes.html">Current routes at Boulders Climbing Gym</a>
Correct: Descriptive alt text on an image link
When a link wraps an image, the alt attribute serves as the link’s accessible name:
<a href="routes.html">
<img src="topo.gif" alt="Current routes at Boulders Climbing Gym">
</a>
Correct: Image and text together in a link
When a link contains both an image and text, use an empty alt attribute on the image to avoid redundancy. The visible text becomes the accessible name:
<a href="routes.html">
<img src="topo.gif" alt="">
Current routes at Boulders Climbing Gym
</a>
Correct: Links with the same name pointing to the same destination
If two links genuinely go to the same place, it’s fine for them to share the same name:
<nav>
<a href="/contact">Contact us</a>
</nav>
<footer>
<a href="/contact">Contact us</a>
</footer>
Both links are labeled “Contact us” and both lead to the same contact page, so there is no ambiguity.
Correct: Using aria-labelledby to build a unique name from existing content
You can reference a nearby heading to create a unique accessible name without adding extra visible text:
<h3 id="tax-heading">Seminole Tax Hike</h3>
<p>City managers propose a 75% increase in property taxes.
<a href="taxhike.html" aria-labelledby="tax-heading tax-link">
<span id="tax-link">Read more</span>
</a>
</p>
<h3 id="mayor-heading">Baby Mayor Elected</h3>
<p>Voters elect the city's youngest mayor ever.
<a href="babymayor.html" aria-labelledby="mayor-heading mayor-link">
<span id="mayor-link">Read more</span>
</a>
</p>
The first link’s accessible name resolves to “Seminole Tax Hike Read more” and the second to “Baby Mayor Elected Read more,” making them distinguishable in a links list.
When a page refreshes automatically, the browser reloads the entire document and moves focus back to the top of the page. This means a user who was partway through reading content or filling out a form suddenly loses their place with no warning. For screen reader users, this is particularly disruptive — they must navigate from the beginning of the page again. Users with cognitive disabilities or those who read more slowly may not have finished processing the content before it disappears. People with mobility impairments who navigate slowly with alternative input devices are also affected, as their progress through the page is reset.
Delayed refreshes also constitute an interruption that the user cannot suppress, postpone, or control. Even if the delay is long (e.g., 60 seconds), the user has no way to opt out or extend the timer.
Related WCAG Success Criteria
This rule relates to two AAA-level WCAG success criteria:
- WCAG 2.2.4 (Interruptions): Interruptions must be able to be postponed or suppressed by the user, except in emergencies. An automatic page refresh is an interruption that the user cannot control.
- WCAG 3.2.5 (Change on Request): Changes of context must be initiated only by user request, or a mechanism must be available to turn off such changes. An automatic refresh or redirect is a change of context that occurs without user action.
How to Fix It
-
Remove the
http-equiv="refresh"attribute from everymetaelement that contains it. -
For redirects, use server-side HTTP redirects (e.g., a
301or302status code) instead of client-side meta refresh. This is more reliable and does not cause accessibility issues. - For periodic content updates, use JavaScript to fetch and update only the changed content without reloading the entire page. Provide users with controls to pause, extend, or disable the automatic updates.
Examples
Incorrect: Using Meta Refresh to Redirect After a Delay
This refreshes the page after 40 seconds, redirecting the user to a new URL without their consent:
<head>
<meta http-equiv="refresh" content="40; url=https://example.com/new-page">
</head>
Incorrect: Using Meta Refresh to Reload the Page
This reloads the current page every 60 seconds:
<head>
<meta http-equiv="refresh" content="60">
</head>
Correct: Server-Side Redirect
Instead of using a meta refresh for redirection, configure your server to return an HTTP redirect. For example, in an .htaccess file:
Redirect 301 /old-page https://example.com/new-page
The HTML page itself contains no meta refresh:
<head>
<meta charset="utf-8">
<title>My Page</title>
</head>
Correct: JavaScript with User Control for Content Updates
If you need to periodically update content, use JavaScript and give the user the ability to control the behavior:
<head>
<meta charset="utf-8">
<title>Live Dashboard</title>
</head>
<body>
<h1>Live Dashboard</h1>
<button id="toggle-refresh">Pause Auto-Refresh</button>
<div id="content">
<p>Dashboard content goes here.</p>
</div>
<script>
let refreshInterval = setInterval(updateContent, 60000);
let isActive = true;
document.getElementById("toggle-refresh").addEventListener("click", function () {
if (isActive) {
clearInterval(refreshInterval);
this.textContent = "Resume Auto-Refresh";
} else {
refreshInterval = setInterval(updateContent, 60000);
this.textContent = "Pause Auto-Refresh";
}
isActive = !isActive;
});
function updateContent() {
// Fetch and update only the content area
}
</script>
</body>
This approach keeps the user in control. They can pause updates when they need more time to read content, and resume when ready — satisfying both WCAG 2.2.4 and 3.2.5.
What This Rule Checks
The axe rule meta-refresh-no-exceptions checks for the presence of the http-equiv="refresh" attribute on any meta element in the document. If found, the rule flags it as a violation regardless of the delay value, since even long delays deny the user control over when the refresh occurs.
Ready to validate your sites?
Start your free trial today.