Accessibility Guides for WCAG 2.0 (AA)
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 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>
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 readers rely on language information to select the correct pronunciation engine for content. Each language has its own sound library with unique pronunciation rules, intonation, and phonetic patterns. When a screen reader encounters a lang attribute with a valid value, it seamlessly switches to the appropriate library. But when the value is invalid — for example, lang="egnlish" instead of lang="en" — the screen reader cannot identify the language and falls back to its default, reading the foreign-language text with completely wrong pronunciation.
This primarily affects blind users and deafblind users who depend on screen readers, as well as users with cognitive disabilities who may rely on text-to-speech tools. Imagine hearing Spanish text pronounced with English phonetic rules — it would be nearly incomprehensible.
Note that this rule specifically checks lang attributes on elements within the page (not the <html> element itself, which is covered by a separate rule). It applies whenever you use the lang attribute to indicate that a section of content is in a different language than the rest of the page.
Related WCAG Success Criteria
This rule maps to WCAG 2.0 / 2.1 / 2.2 Success Criterion 3.1.2: Language of Parts (Level AA). This criterion requires that the human language of each passage or phrase in the content can be programmatically determined, except for proper names, technical terms, and words of indeterminate language. Using a valid language code is essential to meeting this requirement — an invalid code fails to programmatically convey the language.
This rule is also referenced by the Trusted Tester methodology, EN 301 549, and RGAA.
How to Fix It
-
Use valid language codes. Language values must conform to the BCP 47 standard. In practice, this means using two- or three-letter codes from the IANA Language Subtag Registry. Common examples include
en(English),fr(French),es(Spanish),de(German),zh(Chinese),ar(Arabic), andja(Japanese). -
Check for typos. Invalid values are often caused by misspellings (e.g.,
lang="enlish") or using full language names instead of codes (e.g.,lang="French"instead oflang="fr"). -
Use dialect subtags when appropriate. You can specify regional variants such as
en-US(American English),en-GB(British English), orfr-CA(Canadian French). The primary subtag alone is also valid. -
Set the
dirattribute for RTL languages. If you’re marking content in a right-to-left language like Arabic or Hebrew, pair thelangattribute withdir="rtl"to ensure correct text rendering.
Examples
Incorrect: Invalid language code
<p>Welcome to our site. <span lang="spn">Bienvenidos a nuestro sitio.</span></p>
The value spn is not a valid language subtag. Screen readers cannot identify this as Spanish.
Incorrect: Full language name instead of code
<p>Here is a quote: <q lang="Japanese">素晴らしい</q></p>
The value Japanese is not a valid BCP 47 language code.
Correct: Valid two-letter language code
<p>Welcome to our site. <span lang="es">Bienvenidos a nuestro sitio.</span></p>
The value es is the valid language subtag for Spanish.
Correct: Valid language code with regional subtag
<p>The Canadian term is <span lang="fr-CA">dépanneur</span>.</p>
The value fr-CA correctly identifies Canadian French.
Correct: RTL language with direction attribute
<p>The Arabic word for peace is <span lang="ar" dir="rtl">سلام</span>.</p>
The value ar is the valid subtag for Arabic, and dir="rtl" ensures proper text directionality.
Correct: Multiple language switches on one page
<p>
In English we say "goodbye," in German it's
<span lang="de">Auf Wiedersehen</span>, and in Japanese it's
<span lang="ja">さようなら</span>.
</p>
Each inline element uses a valid language code, allowing the screen reader to switch pronunciation engines as needed.
Ready to validate your sites?
Start your free trial today.