WCAG Color Contrast: What Developers Need to Know About Accessibility

1 9 115
calendar_todayschedule2 min read

Color contrast accessibility is one of the most commonly failed WCAG criteria — and one of the easiest to fix once you understand the rules. Here's what the standard requires and how to verify compliance.

What WCAG says

The Web Content Accessibility Guidelines (WCAG) define minimum contrast ratios between text and its background. The key requirements:

WCAG 2.1 Level AA (legal minimum in many jurisdictions):

  • Normal text (under 18pt regular or 14pt bold): 4.5:1
  • Large text (18pt+ regular or 14pt+ bold): 3:1
  • UI components and graphical objects: 3:1
  • Decorative elements, logos, disabled controls: exempt

WCAG 2.1 Level AAA:
  • Normal text: 7:1
  • Large text: 4.5:1

Legal context: WCAG 2.1 AA is legally required for public-sector websites in the EU (EN 301 549), the US (ADA and Section 508), Canada (AODA), and Australia (DDA). Private-sector websites are increasingly covered by ADA case law.

How contrast ratio is calculated

The contrast ratio is calculated from the relative luminance of the two colors:

``

contrast ratio = (L1 + 0.05) / (L2 + 0.05)

`

Where L1 is the lighter color's relative luminance and L2 is the darker color's relative luminance. Relative luminance ranges from 0 (black) to 1 (white).

Relative luminance formula for sRGB:

`javascript
function relativeLuminance(r, g, b) {


// Linearize each channel


const linear = [r, g, b].map(channel => {


const c = channel / 255;


return c <= 0.03928


? c / 12.92


: Math.pow((c + 0.055) / 1.055, 2.4);


});


return 0.2126 linear[0] + 0.7152 linear[1] + 0.0722 * linear[2];


}

function contrastRatio(rgb1, rgb2) {

const l1 = relativeLuminance(...rgb1);

const l2 = relativeLuminance(...rgb2);

const lighter = Math.max(l1, l2);

const darker = Math.min(l1, l2);

return (lighter + 0.05) / (darker + 0.05);

}

contrastRatio([47, 133, 90], [255, 255, 255])

// → 5.08 (passes AA for normal text)

`

For quick checking without code, a color contrast checker calculates the ratio and WCAG verdict instantly when you enter the two hex values.

Common contrast failures

Gray text on white:

Many design systems use light gray text for secondary/placeholder content. Typical failures:

| Text color | Background | Ratio | Result |

|-----------|-----------|-------|--------|

| #999999 | #ffffff | 2.85:1 | Fails AA |

| #767676 | #ffffff | 4.54:1 | Passes AA |

| #595959 | #ffffff | 7.22:1 | Passes AAA |

The lightest gray that passes AA on white is approximately #767676.

White text on color:

| Background | Ratio | Result |


|-----------|-------|--------|


| #3182ce (medium blue) | 4.6:1 | Passes AA |


| #e53e3e (medium red) | 4.07:1 | Fails AA for normal text |


| #d69e2e (medium yellow) | 2.21:1 | Fails AA |

Yellow and light colors almost never achieve sufficient contrast with white text. Use dark text on yellow.

Dark mode issues:

Inverting a light-mode color scheme doesn't guarantee contrast compliance. Check both modes separately.

Automating contrast checks

Storybook a11y addon:

<code>bash <p>npm install --save-dev @storybook/addon-a11y</p> </code>

Add to .storybook/main.js:

<code>javascript <p>module.exports = { addons: ['@storybook/addon-a11y'] };</p> </code>

The a11y addon runs axe accessibility checks on each story, including contrast ratio.

Playwright / Cypress:

`javascript
// Using axe-core with Playwright


import { checkA11y } from 'axe-playwright';

test('passes contrast checks', async ({ page }) => {

await page.goto('/');

await checkA11y(page, null, {

runOnly: { type: 'tag', values: ['wcag2aa'] }

});

});

`

Lighthouse:

Run from the command line:


<code>bash <p>npx lighthouse https://example.com --only-categories=accessibility --output json</p> </code> Lighthouse's accessibility score covers contrast ratios for text elements automatically. axe-core in browser:
<code>javascript <p>// Install: npm install axe-core</p> <p>import axe from 'axe-core';</p> <p>axe.run(document, { runOnly: { type: 'tag', values: ['wcag2aa'] } })</p> <p>.then(results => {</p> <p>results.violations.forEach(v => {</p> <p>console.log(v.id, v.description, v.nodes);</p> <p>});</p> <p>});</p> </code> Design tool support Figma: Select a text layer, check the Fill section in the right panel — Figma shows the contrast ratio and AA/AAA pass/fail against the element behind it. The "Figma A11y" plugin and "Contrast" plugin provide more detailed analysis. Sketch: The "Stark" plugin provides WCAG contrast checking inline. Adobe XD: Built-in accessibility checker includes contrast. The "Able" plugin provides additional detail. Fixing low-contrast designs When a design fails contrast: Option 1: Darken the text color (preserves the design's background)
`css
/ Failed: #999 on white /
color: #999999; / Fixed: darkened to pass AA / color: #767676;
` Option 2: Lighten the background
For text on a dark background that's too dark — lighten the background until contrast passes. Option 3: Use an intermediate shade for all states
If a brand color fails contrast as text-on-white, use a darker shade:
<code>css <p>:root {</p> <p>--brand: hsl(145, 48%, 45%); /<em> Too light for text on white </em>/</p> <p>--brand-accessible: hsl(145, 48%, 35%); /<em> Darker, passes AA </em>/</p> <p>}</p> </code>`

Check WCAG 3.0:

WCAG 3.0 introduces the APCA (Advanced Perceptual Contrast Algorithm), which is more accurate for low-contrast situations and replaces the simple ratio with a perceptual model. It's not yet a legal standard, but design tools and validators are beginning to include it alongside the traditional ratio.


Color contrast is a straightforward accessibility requirement with clear pass/fail criteria. Building contrast checking into your design review and CI pipeline catches failures before they reach production.

Originally published at https://snappytools.app/color-contrast-checker/

🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

I’m a Senior Dev and I’ve Forgotten How to Think Without a Prompt

Karol Modelski - Mar 19

Color Contrast for Web Accessibility: WCAG AA vs AAA Explained

SnappyTools - May 14

Dog CT Scan Cost: What Pet Parents Need to Know

Huifer - Feb 6

TypeScript Complexity Has Finally Reached the Point of Total Absurdity

Karol Modelski - Apr 23

Sovereign Intelligence: The Complete 25,000 Word Blueprint (Download)

Pocket Portfolio - Apr 1
chevron_left
2.4k Points125 Badges
101Posts
0Comments
SnappyTools builds free, fast, browser-based tools for developers, writers, and designers. No signup... Show more

Related Jobs

View all jobs →

Commenters (This Week)

2 comments
1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!