If you have ever squinted at a website trying to read light grey text on a white background, you have experienced poor color contrast. It is uncomfortable for everyone. For people with low vision, color blindness, or cognitive disabilities, it makes content completely inaccessible.
The Web Content Accessibility Guidelines (WCAG) give us a mathematical standard for color contrast, and every modern web developer should understand it.
What Is the Contrast Ratio?
The contrast ratio compares the relative luminance of two colors. Luminance is a measure of how much light a color emits — white is 1.0, black is 0.0.
The formula is:
``
ratio = (L1 + 0.05) / (L2 + 0.05)
`
Where L1 is the lighter color's luminance and L2 is the darker one's. Ratios range from 1:1 (no contrast, same color) to 21:1 (black on white, maximum contrast).
To get luminance from an RGB value, you first linearize each channel, then apply weights that reflect how the human eye perceives brightness:
`javascript
function getLuminance(r, g, b) {
const [rs, gs, bs] = [r, g, b].map(c => {
c /= 255;
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
});
return 0.2126 rs + 0.7152 gs + 0.0722 * bs;
}
function contrastRatio(rgb1, rgb2) {
const l1 = getLuminance(...rgb1);
const l2 = getLuminance(...rgb2);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
`
WCAG Levels: AA vs AAA
WCAG defines two conformance levels for contrast:
WCAG AA — the minimum standard required by most accessibility laws (including ADA, EN 301 549, and Section 508 in the US):
- Normal text (under 18pt or 14pt bold): 4.5:1 minimum
- Large text (18pt+ or 14pt+ bold) and UI components: 3:1 minimum
WCAG AAA — the enhanced standard, recommended for critical text:
- Normal text: 7:1 minimum
- Large text: 4.5:1 minimum
Most teams aim for AA across the board and AAA for body copy on high-traffic pages.
Why the Green/Grey Traps Are So Common
Web designers often use brand colors without checking contrast. A company palette might include a medium-saturation green that looks vibrant in the style guide but fails AA when used as text on white.
Common failure patterns:
- Light grey text on white backgrounds — a common UI pattern for secondary text that looks elegant but often fails at 2:1 or 3:1
- Coloured text on coloured backgrounds — two medium-saturation colours in the same value range can clash at only 1.5:1
- White text on mid-tone brand colours — a teal, orange, or green at 50% brightness barely reaches 3:1 against white
Fixing Contrast Without Losing Your Design
You do not usually need to replace your brand colors. Adjust lightness:
- For text on white: darken the text color until it passes
- For white text on a colored background: darken the background until it passes
- For body copy: use black or near-black (#1a1a1a) on white — instant 19:1 ratio
In CSS, the color-mix() function (now well-supported) makes this easy:
`css
/ Start with your brand color /
--brand: #4a9a6f;
/ For text, darken it /
--text-primary: color-mix(in srgb, var(--brand) 60%, black);
/ For backgrounds, lighten it /
--bg-accent: color-mix(in srgb, var(--brand) 20%, white);
`
Testing Your Palette
You can check contrast ratios manually using the formula above, or use a browser-based tool. The SnappyTools Color Contrast Checker lets you enter two hex colors, shows the contrast ratio, and tells you instantly whether it passes WCAG AA and AAA for normal text, large text, and UI components. It runs entirely in the browser.
For systematic palette audits, use browser DevTools. In Chrome, opening any color in DevTools shows the contrast ratio inline and flags AA/AAA status with a warning icon.
Automated Testing in CI
Manual checking is not enough for large codebases. Add automated contrast checking to your CI pipeline:
- axe-core — integrates with Jest, Cypress, and Playwright. Runs a full accessibility audit including contrast failures.
- Storybook a11y addon — shows contrast issues per component during development
- Lighthouse — checks contrast for rendered pages and reports failures in the accessibility audit section
A typical axe-core integration in Playwright:
`javascript
import { checkA11y } from 'axe-playwright';
test('homepage passes contrast checks', async ({ page }) => {
await page.goto('/');
await checkA11y(page, null, {
runOnly: { type: 'tag', values: ['wcag2aa'] },
});
});
``
This catches regressions before they reach production.
Summary
- Contrast ratio measures luminance difference between two colors (1:1 to 21:1)
- WCAG AA: 4.5:1 for normal text, 3:1 for large text — required by most accessibility laws
- WCAG AAA: 7:1 for normal text — recommended for body copy
- The most common failures are light grey on white and brand colors without luminance adjustment
- Fix by darkening text or darkening the background color — rarely need to change the hue
- Automate with axe-core in CI to prevent regressions
Accessible color contrast is one of the cheapest accessibility wins available — it costs nothing to check and usually takes five minutes to fix.
Originally published at https://snappytools.app/color-contrast-checker/