How to Fix Color Contrast Issues for WCAG 2.1 AA
Color contrast problems are some of the most common accessibility issues on modern websites. A page can look clean, stylish, and brand-consistent while still being difficult to read for people with low vision, color vision differences, glare, aging eyes, or temporary viewing limitations such as a dim screen or bright sunlight.
Contrast is not only about black text on a white background. It affects buttons, links, form labels, placeholder text, error messages, navigation states, badges, icons, charts, focus indicators, and disabled controls. It also affects whether users can tell which elements are interactive and which information is important.
A color contrast issue happens when the visual difference between foreground content and the background is too small. The most obvious example is light gray text on a white background. But the problem can appear in subtle places: pale blue links, thin text over hero images, low-contrast button labels, muted form hints, gray placeholder text, disabled-looking active buttons, and chart colors that cannot be distinguished from one another.
ADA CodeFix flags color contrast issues because they are frequent, measurable, and often easy to improve. This guide explains what contrast means, where it breaks, how WCAG treats contrast, and how to fix common contrast problems without destroying your brand design.
This page is informational and is not legal advice. Accessibility requirements can vary by site, organization, and jurisdiction. For legal guidance, consult qualified counsel. ADA CodeFix can help identify likely accessibility issues and generate developer-reviewable fixes, but automated scans do not replace manual review.
What color contrast means
Color contrast is the difference in perceived brightness between two colors. On a website, the most common contrast relationship is text color against its background.
For example:
.low-contrast {
color: #b8b8b8;
background: #ffffff;
}This may look soft and elegant, but it can be hard to read. A stronger version might be:
.readable-text {
color: #333333;
background: #ffffff;
}Contrast is especially important for smaller text, thin fonts, and content that users must read to complete a task. A subtle tone difference that looks fine in a designer's mockup can become unreadable on a phone in sunlight or for a reader whose vision is not perfectly sharp.
WCAG criteria related to contrast
WCAG 1.4.3 — Contrast Minimum
WCAG 2.1 AA generally requires regular text to have a contrast ratio of at least 4.5:1 against its background. Large text generally requires at least 3:1.
Large text usually means text that is at least 18 points, or 14 points if bold. In web terms, that roughly maps to larger headings or prominent display text, but you should check the actual CSS size and weight rather than guessing.
WCAG 1.4.11 — Non-text Contrast
Important non-text visual information also needs sufficient contrast. This includes button boundaries, input borders, icons, focus indicators, chart elements, and visual states that communicate meaning. A button that has readable text but an almost-invisible border can still be a contrast problem because users may not recognize it as a button at all.
WCAG 1.4.1 — Use of Color
Color should not be the only way information is conveyed. If an error message is shown only by turning a field border red, some users may miss it. Add text, icons, or programmatic error messaging so the meaning is available without relying on color perception.
Common color contrast failures
Failure 1: Light gray body text
Many sites use gray text to create a softer visual hierarchy.
Problem:
body {
color: #9ca3af;
background: #ffffff;
}This can be too faint for body copy. Body text needs to be comfortable for long reading.
Better:
body {
color: #1f2937;
background: #ffffff;
}
.text-muted {
color: #4b5563;
}Muted text can still exist, but it should remain readable. The point is not to remove visual hierarchy. The point is to make sure the lower-emphasis tone still has enough contrast to be comfortably read.
Failure 2: Low-contrast buttons
A button can fail contrast in two ways: the button label may not contrast against the button background, or the button boundary may not contrast against the surrounding page.
Problem:
.button {
background: #dbeafe;
color: #93c5fd;
}Better:
.button {
background: #1d4ed8;
color: #ffffff;
}For secondary buttons:
.button-secondary {
background: #ffffff;
color: #1f2937;
border: 2px solid #374151;
}If the boundary is important for understanding that an element is a button, the border needs enough contrast too. A white-on-white secondary button with a near-invisible gray border may be technically clickable, but users may not know it is interactive.
Failure 3: Text over images
Hero sections often put white text over a photo. This can work, but only if the image is controlled or the text has a strong overlay.
Problem:
.hero {
background-image: url("/images/office.jpg");
color: white;
}If the image has light areas, the text may disappear.
Better:
.hero {
position: relative;
background-image: linear-gradient(
rgba(0, 0, 0, 0.65),
rgba(0, 0, 0, 0.65)
),
url("/images/office.jpg");
color: #ffffff;
}You can also place text in a solid or semi-solid panel:
.hero-card {
background: rgba(0, 0, 0, 0.75);
color: #ffffff;
padding: 2rem;
border-radius: 1rem;
}Text over images should be tested at multiple screen sizes because cropping changes the background behind the text. A hero that looks fine on a desktop wide screen may put the headline directly over a bright sky on a mobile breakpoint.
Failure 4: Placeholder text used as instructions
Placeholder text is often low contrast by default. It is also not a substitute for a label.
Problem:
input::placeholder {
color: #cbd5e1;
}Better:
label {
color: #111827;
}
.form-hint {
color: #4b5563;
}
input::placeholder {
color: #6b7280;
}Even if placeholder text meets contrast, it should not be the only way users understand the field. Use visible labels and help text so the meaning persists once the user starts typing.
Failure 5: Links that are only slightly different from text
Links need to be distinguishable from surrounding text. A low-contrast blue or a barely visible underline can be hard to notice.
Problem:
.prose a {
color: #7aa7dd;
text-decoration: none;
}Better:
.prose a {
color: #0645ad;
text-decoration: underline;
text-underline-offset: 0.15em;
}If links are only identified by color, consider adding underlines or another non-color cue. This is one of the cases where color contrast and use of color overlap: even a high-contrast link color is not enough by itself if color is the only signal that the text is a link.
Failure 6: Error messages that rely only on red
Problem:
<input class="error" id="email" type="email">.error {
border-color: #ef4444;
}Better:
<label for="email">Email address</label>
<input
id="email"
type="email"
aria-invalid="true"
aria-describedby="email-error"
>
<p id="email-error" class="error-message">
Enter a valid email address.
</p>input[aria-invalid="true"] {
border-color: #b91c1c;
}
.error-message {
color: #991b1b;
font-weight: 600;
}Now the error is communicated with text and programmatic association, not just color. A user who cannot perceive the red border still receives the message through the text and through assistive technology.
How to fix contrast step by step
Step 1: Identify foreground and background pairs
For each important element, identify the foreground color and background color. Check:
- Body text
- Headings
- Links
- Buttons
- Form labels
- Placeholder text
- Helper text
- Error messages
- Navigation items
- Badges
- Icons
- Focus indicators
- Charts
- Text over images
Do not assume a color is safe because it looks readable on your monitor. Real users browse on a wide range of devices, often in non-ideal lighting.
Step 2: Check contrast ratios
Use a contrast checker or browser dev tools to measure contrast. For normal text, aim for at least 4.5:1. For large text and important non-text UI components, aim for at least 3:1.
When in doubt, exceed the minimum. A ratio that barely passes on a perfect monitor may be harder to read in real-world conditions like glare, smaller font sizes, or rendered text anti-aliasing differences across browsers.
Step 3: Adjust the design token, not just one element
If your site uses design tokens, fix the token rather than patching one component at a time.
Problem:
:root {
--color-muted: #a1a1aa;
}Better:
:root {
--color-muted: #52525b;
}Then components using --color-muted become more readable across the site. Treating the token as the source of truth prevents the same low-contrast pattern from being duplicated in dozens of files.
Step 4: Create accessible color pairs
Do not evaluate colors in isolation. Create approved pairs.
Example:
:root {
--text-on-light: #111827;
--muted-on-light: #4b5563;
--link-on-light: #0645ad;
--text-on-dark: #ffffff;
--muted-on-dark: #e5e7eb;
--link-on-dark: #bfdbfe;
--primary-bg: #1d4ed8;
--primary-text: #ffffff;
}This prevents designers and developers from mixing colors randomly. Each pair is approved, tested, and reusable across components.
Step 5: Test states
Contrast can fail in hover, focus, active, selected, disabled, and error states. Check:
.button:hover
.button:focus-visible
.button:active
.button[aria-pressed="true"]
.input:disabled
.input[aria-invalid="true"]If a button becomes low-contrast on hover, that is still a problem. If a selected tab is shown only by a subtle color shift, it may be hard to perceive. Treat each state as a distinct visual combination that needs to be measured.
Disabled states and contrast
Disabled controls are a tricky area. WCAG contrast requirements have exceptions for inactive components, but users still need to understand the interface. Very faint disabled buttons can create confusion, especially if users do not know why the action is unavailable.
Instead of making disabled controls nearly invisible, provide context.
<button type="button" disabled>
Continue
</button>
<p id="continue-help">Complete all required fields to continue.</p>If a control is important to the task, explain what is needed to enable it. A user who sees a faded button without context may assume the page is broken.
Charts and color contrast
Charts often fail because they rely on similar colors or color alone.
Problem:
- Green means passed
- Red means failed
- Yellow means needs review
A user with color vision differences may not distinguish the categories. Add labels, patterns, icons, or direct text.
Better chart legend:
<ul>
<li><span class="legend passed"></span> Passed checks</li>
<li><span class="legend failed"></span> Failed checks</li>
<li><span class="legend review"></span> Needs review</li>
</ul>Even better, label the data directly in the chart or provide a table.
<table>
<caption>Accessibility scan results by status</caption>
<thead>
<tr>
<th>Status</th>
<th>Count</th>
</tr>
</thead>
<tbody>
<tr>
<td>Passed</td>
<td>48</td>
</tr>
<tr>
<td>Failed</td>
<td>12</td>
</tr>
<tr>
<td>Needs review</td>
<td>7</td>
</tr>
</tbody>
</table>How ADA CodeFix detects color contrast issues
ADA CodeFix can identify many text contrast failures by comparing foreground and background colors in rendered page states. It may flag:
- Text below contrast thresholds
- Low-contrast buttons
- Low-contrast links
- Low-contrast form labels or helper text
- Placeholder text that may be too faint
- Icons or controls that may fail non-text contrast
- Text over image or gradient backgrounds that needs review
- Focus indicators that may not be visible enough
Some contrast issues require manual judgment. Text over images, transparent overlays, dynamic states, and custom canvas/SVG graphics can be difficult for automated tools to evaluate perfectly. Pairing automated scanning with a short manual review tends to catch the most issues.
Manual contrast review checklist
Use this checklist before publishing a page.
- Does normal body text meet contrast expectations?
- Do small labels, captions, helper text, and footnotes remain readable?
- Do buttons have readable labels?
- Do secondary buttons have visible boundaries?
- Are links distinguishable from surrounding text?
- Is text over images protected by a strong overlay or solid background?
- Are placeholder text and form hints readable?
- Are error states communicated with text, not color alone?
- Are focus indicators visible?
- Are icons and control boundaries visible?
- Do charts use labels or patterns, not only color?
- Do hover, active, selected, and focus states remain readable?
- Does the page still work in bright light or at low screen brightness?
Common mistakes to avoid
Mistake 1: Designing only on a high-end monitor
A color pair that looks fine on a calibrated monitor may fail on older laptops, low-brightness screens, mobile devices, or in sunlight. Try opening the page on a budget phone in daylight before assuming a subtle gray is "readable enough."
Mistake 2: Treating gray as automatically safe
Gray text can pass or fail depending on the exact value and background. Measure it. A single hex value change can move a color from failing to passing or back again.
Mistake 3: Fixing only body text
Buttons, links, placeholders, captions, badges, focus rings, and icons matter too. A site can have perfectly readable paragraphs and still fail at the call-to-action.
Mistake 4: Using brand colors without accessible pairings
A brand palette is not automatically an accessible UI system. Create approved foreground/background combinations so designers and developers do not need to guess which pairs are safe.
Mistake 5: Relying only on color for meaning
Use text, labels, icons, or shapes in addition to color. Even high-contrast color is not enough by itself if color is the only signal that something is important, required, or in an error state.
Platform-specific notes
WordPress
Theme settings often control body text, link colors, buttons, and headings. Check global styles, page-builder modules, contact forms, and plugin-generated components. Plugins can inject their own button styles that bypass theme tokens, so test those components individually.
Shopify
Review product cards, sale badges, variant selectors, checkout-adjacent forms, cart drawers, and email signup sections. Sale prices and compare-at prices often use colors that need contrast testing.
Webflow
Webflow makes it easy to apply brand colors visually, but contrast should be checked on every breakpoint and component state. Pay special attention to text over images and hover states.
React and Next.js
If you use Tailwind or design tokens, define accessible color pairs. Do not rely on ad hoc classes copied across components.
Example:
<a className="text-blue-800 underline underline-offset-4 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4">
Read the guide
</a>Centralizing color decisions in tokens or utility classes keeps the system consistent and easier to audit later.
Final takeaway
Color contrast is a design, content, and accessibility issue at the same time. The goal is not to make every site black and white. The goal is to make important information readable and interactive elements understandable.
The best fixes usually come from building an accessible color system: readable text colors, approved button pairs, visible links, strong focus states, clear error styles, and chart patterns that do not rely on color alone. ADA CodeFix can help identify likely contrast failures and suggest developer-reviewable fixes, but contrast should also be checked manually in real page context, on real devices, in real lighting conditions.
Sources
- W3C WCAG Understanding 1.4.3 Contrast Minimum (opens in new tab)
- W3C WCAG Understanding 1.4.11 Non-text Contrast (opens in new tab)
- W3C WAI Color Contrast (opens in new tab)
- W3C WCAG Quick Reference (opens in new tab)
Run a free WCAG 2.1 AA scan on your site
ADA CodeFix scans your pages, identifies likely WCAG failures, and generates developer-reviewable code fixes for labels, focus styles, alt text, color contrast, and more.
Scan My Site FreeRelated guides
Brand-friendly focus indicators that stay readable against any background.
Image alt text fixesWrite meaningful alt text for content, decorative, and functional images.
WCAG 1.4.3 Contrast (Minimum)The 4.5:1 and 3:1 ratios for normal and large text.
WCAG 1.4.1 Use of ColorWhy color alone is not enough to convey meaning.