How to Fix Color Contrast Failures
Your accessibility scanner flagged one or more color contrast issues. This guide explains exactly what was detected, why it matters, and how to fix each type of failure with specific CSS changes and color values.
What the Scanner Found
Axe-core checks three contrast-related rules:
- color-contrast — Text does not meet the WCAG AA minimum contrast ratio. This is the most common failure and the one you must fix for compliance.
- color-contrast-enhanced — Text meets AA but fails the stricter AAA enhanced contrast. This is a best practice, not a legal requirement for most sites.
- link-in-text-block — A link inside a paragraph is not distinguishable from surrounding text. The link color must have a 3:1 contrast ratio against the surrounding text color, or the link must have a non-color indicator like an underline.
The WCAG Contrast Requirements
WCAG 1.4.3 Contrast Minimum (Level AA) is the standard you need to meet. The required ratios are:
- 4.5:1 for normal-sized text
- 3:1 for large text
WCAG 1.4.6 Contrast Enhanced (Level AAA) is a higher bar, recommended but not required by most regulations:
- 7:1 for normal-sized text
- 4.5:1 for large text
WCAG 1.4.11 Non-text Contrast (Level AA) covers everything that is not text:
- 3:1 for user interface components — button borders, form input borders, checkbox outlines, toggle switches, custom radio buttons
- 3:1 for meaningful graphics — icons that convey information, chart segments, data visualization lines
What Counts as "Large Text"?
Large text qualifies for the relaxed 3:1 ratio (AA) or 4.5:1 ratio (AAA). The thresholds are:
- 18pt (24px) at regular weight (400 or lighter)
- 14pt (18.66px) at bold weight (700 or heavier)
These thresholds are based on CSS computed values. If your font-size is set in rem or em, calculate the pixel equivalent. For most browsers with default settings, 1.5rem equals 24px and qualifies as large text at regular weight.
How Contrast Ratios Work
The contrast ratio is calculated from the relative luminance of two colors. Relative luminance measures how bright a color appears to the human eye, on a scale from 0 (black) to 1 (white). The formula is:
contrast ratio = (L1 + 0.05) / (L2 + 0.05)
Where L1 is the luminance of the lighter color and L2 is the luminance of the darker. For example, pure white has a luminance of 1.0 and pure black has 0.0, giving (1.0 + 0.05) / (0.0 + 0.05) = 21:1. In practice, you never need to calculate this by hand — use a tool.
Fix: Text on Solid Backgrounds
This is the most common scenario. You have text of one color on a background of another, and the contrast ratio is too low. You have two options: darken the text or lighten the background (or the reverse for dark themes).
/* Before: #999 on #fff = 2.85:1 — FAILS AA */
.subtitle {
color: #999999;
background: #ffffff;
}
/* After: #767676 on #fff = 4.54:1 — PASSES AA */
.subtitle {
color: #767676;
background: #ffffff;
}
/* Or darken further: #595959 on #fff = 7.0:1 — PASSES AAA */
.subtitle {
color: #595959;
background: #ffffff;
}
The value #767676 is the lightest gray that passes AA contrast against white. Memorize it — it is the single most useful color value in accessibility work.
Fix: Text on Images and Gradients
When text sits on a photograph or gradient, contrast varies across the image. The scanner typically flags the worst-case area. Three reliable fixes:
/* Option 1: Semi-transparent overlay behind the entire image area */
.hero {
position: relative;
}
.hero::before {
content: '';
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.55);
z-index: 1;
}
.hero-text {
position: relative;
z-index: 2;
color: #ffffff;
}
/* Option 2: Background behind just the text */
.caption-on-image {
background: rgba(0, 0, 0, 0.7);
color: #ffffff;
padding: 0.25em 0.5em;
}
/* Option 3: Text shadow for subtle protection */
.text-on-photo {
color: #ffffff;
text-shadow:
0 1px 3px rgba(0, 0, 0, 0.8),
0 0 8px rgba(0, 0, 0, 0.6);
}
Text shadow alone is not considered a reliable contrast method by WCAG because the shadow dimensions are not guaranteed. Use it as a supplement, not the sole fix. The overlay or background approach is more robust.
Fix: Placeholder Text
Placeholder text must meet the same 4.5:1 contrast ratio as regular text. Most browser defaults fail this. It is one of the most commonly missed issues.
/* Browser default placeholder is typically around #a9a9a9 on #fff = 2.32:1 — FAILS */
/* Fix: darken the placeholder */
input::placeholder {
color: #767676; /* 4.54:1 on white — PASSES AA */
}
/* If your input has a dark background: */
.dark-input::placeholder {
color: #a3a3a3; /* adjust to achieve 4.5:1 against your background */
}
If possible, avoid relying on placeholder text for important instructions. Use a visible label or helper text below the input instead.
Fix: Disabled Elements
Disabled elements (elements with the disabled attribute or aria-disabled="true") are exempt from WCAG contrast requirements. However, users still need to perceive that a control exists, even if they cannot interact with it.
/* Acceptable: low contrast but still visible */
button:disabled {
color: #a3a3a3;
background: #e5e5e5;
border: 1px solid #d4d4d4;
cursor: not-allowed;
opacity: 1; /* avoid opacity below 0.5 — the element becomes invisible */
}
A common mistake is setting opacity: 0.3 on disabled buttons, which makes them nearly invisible. Keep disabled elements visible enough that users understand a control exists in that position.
Fix: Focus Indicators
WCAG 2.4.11 (Focus Appearance, Level AA in WCAG 2.2) requires that focus indicators have a 3:1 contrast ratio against adjacent colors. This means the focus ring must contrast with both the component and the surrounding background.
/* Poor: thin light outline that disappears on white backgrounds */
button:focus {
outline: 1px solid #a3c5f7;
}
/* Better: thick, high-contrast focus ring with offset */
button:focus-visible {
outline: 2px solid #1a56db; /* 3:1+ against white background */
outline-offset: 2px;
}
/* For dark backgrounds, use a lighter ring */
.dark-section button:focus-visible {
outline: 2px solid #93c5fd;
outline-offset: 2px;
}
Never use outline: none without providing an alternative focus style. Removing focus indicators entirely is a keyboard accessibility failure.
Fix: Icons and UI Components
Borders, icons, checkboxes, form inputs, and other non-text UI elements need 3:1 contrast against their background. This applies to the visual boundary that defines the component.
/* Before: light border on form input — #d1d5db on #fff = 1.77:1 — FAILS */
input {
border: 1px solid #d1d5db;
}
/* After: darker border — #8b8b8b on #fff = 3.45:1 — PASSES */
input {
border: 1px solid #8b8b8b;
}
/* Custom checkbox: the unchecked border must contrast with background */
.checkbox {
width: 18px;
height: 18px;
border: 2px solid #6b7280; /* 5.03:1 on white — PASSES */
border-radius: 3px;
}
/* Icons conveying meaning (not purely decorative) */
.status-icon {
color: #6b7280; /* 5.03:1 on white — PASSES */
}
Purely decorative icons — those that duplicate adjacent text or serve no informational purpose — are exempt from contrast requirements.
Fix: Links in Text Blocks
When links appear inside a paragraph, the link color must be distinguishable from the surrounding text. WCAG gives you two options:
- The link color has a 3:1 contrast ratio against the surrounding text color, AND the link also meets 4.5:1 against the background.
- The link has a non-color visual indicator (typically an underline) that is always visible, not just on hover.
/* Option 1: Color alone (must meet both contrast requirements) */
p {
color: #333333;
}
p a {
color: #0044cc; /* 3:1 against #333 body text, AND 8.67:1 against #fff background */
text-decoration: none;
}
/* Option 2: Underline (simpler, recommended) */
p a {
color: #2563eb; /* 4.67:1 against white — PASSES */
text-decoration: underline;
}
/* Avoid: removing underline without sufficient color contrast */
p a {
color: #3b82f6; /* 3.13:1 against white — FAILS for normal text */
text-decoration: none; /* no visual indicator either — double failure */
}
Adjusting Brand Colors That Fail
Your brand's primary color may not pass contrast requirements when used as text on a white background. The solution is to find a darker shade of the same hue.
/* Brand blue that fails: #3b82f6 on #fff = 3.13:1 — FAILS AA */
/* Adjusted brand blue: #2563eb on #fff = 4.67:1 — PASSES AA */
/* Further darkened: #1d4ed8 on #fff = 6.35:1 — PASSES AA, nearly AAA */
:root {
--brand-primary: #2563eb; /* use for text on white */
--brand-primary-light: #3b82f6; /* use only for large text or non-text elements */
--brand-primary-bg: #eff6ff; /* light tint for backgrounds */
}
The key technique: keep the same hue (the H in HSL), reduce lightness. In HSL terms, #3b82f6 is roughly hsl(217, 91%, 60%). Shifting lightness to 54% gives #2563eb, which passes. You maintain the brand feel while meeting requirements.
Hover and Focus State Contrast
Interactive states must also meet contrast requirements. A hover color that passes contrast when static may fail during interaction, or vice versa.
/* All three states must pass contrast independently */
.btn-primary {
background: #2563eb; /* 4.67:1 against white text — PASSES */
color: #ffffff;
}
.btn-primary:hover {
background: #1d4ed8; /* 6.35:1 — PASSES (darker on hover is safe) */
}
.btn-primary:focus-visible {
background: #1d4ed8;
outline: 2px solid #1e40af;
outline-offset: 2px;
}
CSS Techniques for Maintainable Contrast
Use CSS custom properties to define a contrast-safe color palette once, then reference it everywhere. This makes future adjustments trivial.
:root {
--text-primary: #111827; /* 17.15:1 on white */
--text-secondary: #4b5563; /* 7.45:1 on white — PASSES AAA */
--text-muted: #6b7280; /* 5.03:1 on white — PASSES AA */
--border: #8b8b8b; /* 3.45:1 on white — PASSES non-text */
--bg-surface: #ffffff;
--bg-muted: #f9fafb;
}
/* Dark mode: recalculate all ratios against the dark background */
@media (prefers-color-scheme: dark) {
:root {
--text-primary: #f9fafb; /* 17.45:1 on #111827 */
--text-secondary: #d1d5db; /* 10.26:1 on #111827 */
--text-muted: #9ca3af; /* 5.57:1 on #111827 */
--border: #6b7280; /* 3.44:1 on #111827 */
--bg-surface: #111827;
--bg-muted: #1f2937;
}
}
The prefers-contrast media query lets you increase contrast for users who request it at the OS level:
@media (prefers-contrast: more) {
:root {
--text-secondary: #333333; /* boosted from 7.45:1 to 12.63:1 */
--text-muted: #4b5563; /* boosted from 5.03:1 to 7.45:1 */
--border: #555555; /* boosted from 3.45:1 to 8.59:1 */
}
}
Common Problem Patterns with Fixes
Light gray text on white — the single most common failure in modern web design:
#ccccccon#ffffff= 1.61:1 — fails badly#999999on#ffffff= 2.85:1 — still fails#767676on#ffffff= 4.54:1 — passes AA#595959on#ffffff= 7.0:1 — passes AAA
Blue links on dark backgrounds:
#3b82f6on#1f2937= 3.64:1 — fails for normal text#60a5faon#1f2937= 5.74:1 — passes AA#93c5fdon#1f2937= 8.87:1 — passes AAA
Success/error states with insufficient contrast:
#22c55e(green) on#ffffff= 2.09:1 — fails. Use#15803d= 4.88:1#ef4444(red) on#ffffff= 3.93:1 — fails. Use#dc2626= 4.63:1#f59e0b(amber) on#ffffff= 1.83:1 — fails. Use#92400e= 7.27:1
Tools for Finding the Right Colors
- WebAIM Contrast Checker (webaim.org/resources/contrastchecker) — Enter foreground and background hex values, get instant pass/fail for AA and AAA at both normal and large text sizes. Includes a lightness slider to find the nearest passing color.
- Chrome DevTools contrast picker — In the Elements panel, click any color swatch in the Styles pane. The color picker shows the contrast ratio and draws two curved lines on the color spectrum: one for AA (4.5:1) and one for AAA (7:1). Pick any color below the line to pass.
- Firefox Accessibility Inspector — The Accessibility tab shows contrast ratios for every text node and flags failures. Useful for scanning an entire page at once.
- Figma plugins — "Stark" and "A11y - Color Contrast Checker" check contrast directly in your design files, catching issues before they reach code.
- Colour Contrast Analyser (CCA) — Desktop app with an eyedropper tool that samples colors from anywhere on your screen, useful for testing PDFs, images, and native applications.
Testing Your Fix
After making changes, verify the fix in three ways:
- Browser DevTools — Inspect the element, click the color swatch in the Styles panel. Chrome and Edge display the computed contrast ratio directly. If you see a green checkmark, you pass.
- Re-run the scanner — Scan the page again to confirm the
color-contrastviolation is resolved. Automated scanners can only check text on solid, opaque backgrounds. Text on gradients or images may require manual verification. - Visual check — Look at the page yourself. If the text feels hard to read in a well-lit room, it probably needs more contrast regardless of what the numbers say.
If the scanner still flags the element after your fix, check for these common causes: the element inherits a different background color than you expect, a pseudo-element adds a background layer, the element has partial opacity (rgba or opacity) that reduces effective contrast, or the computed font-size is smaller than you intended (making the "large text" exception inapplicable).
Je váš web prístupný?
Skenujte svoj web zadarmo a získajte WCAG skóre za pár minút.
Skenovať web zadarmo