← Blog
Web DesignApril 29, 20259 min

WCAG Color Contrast: Building Accessible Color Systems

How contrast ratio requirements work, the difference between WCAG AA and AAA, and practical strategies for designing accessible color systems.

Why Contrast Matters

Approximately 8% of men and 0.5% of women have some form of color vision deficiency. Many more people have low vision, are in bright sunlight, or use older screens with limited dynamic range. Sufficient color contrast ensures text is readable regardless of these conditions.

The Web Content Accessibility Guidelines (WCAG) define minimum contrast ratios as part of their Success Criteria under 1.4.3 Contrast (Minimum) and 1.4.6 Contrast (Enhanced).

What Is a Contrast Ratio?

Contrast ratio compares the relative luminance of two colors. Luminance is a measure of perceived brightness, accounting for how the human eye is more sensitive to green than red or blue.

Contrast ratio formula

Contrast Ratio = (L1 + 0.05) / (L2 + 0.05) Where L1 = relative luminance of lighter color L2 = relative luminance of darker color Range: 1:1 (no contrast) to 21:1 (black on white)

WCAG AA vs AAA

Level AA (minimum legal standard in many jurisdictions)

  • Normal text (under 18pt, or 14pt bold): 4.5:1 minimum
  • Large text (18pt+ or 14pt bold+): 3:1 minimum
  • UI components and graphics: 3:1 against adjacent colors

Level AAA (enhanced)

  • Normal text: 7:1 minimum
  • Large text: 4.5:1 minimum

AAA is aspirational — not all content can meet AAA without sacrificing design quality. However, meeting AAA for body text significantly improves legibility for all users, not just those with disabilities.

Calculating Relative Luminance

function relativeLuminance(r, g, b) {
  const sRGB = [r, g, b].map(c => {
    c /= 255;
    return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
  });
  return 0.2126 * sRGB[0] + 0.7152 * sRGB[1] + 0.0722 * sRGB[2];
}

function contrastRatio(color1, color2) {
  const l1 = relativeLuminance(...color1);
  const l2 = relativeLuminance(...color2);
  const lighter = Math.max(l1, l2);
  const darker  = Math.min(l1, l2);
  return (lighter + 0.05) / (darker + 0.05);
}

Common Failures

  • Light gray on white: Very popular in flat design — often fails AA. #999 on white is only 2.85:1
  • Brand colors on white backgrounds: Saturated colors (yellow, light blue) often have luminance too close to white
  • Placeholder text: By convention styled lighter than input text — frequently below 3:1
  • Disabled states: WCAG exempts disabled elements, but consider users who can't tell a field is disabled
  • Text on images/gradients: The contrast varies across the image — test the worst-case area

Building an Accessible Palette

Rather than checking colors after the fact, design your palette with contrast in mind:

  1. Choose a base text color with luminance near 0 (e.g., #1a1a1a)
  2. Generate tints and shades of your brand color, testing each against your backgrounds
  3. Reserve your lightest brand shades for large text or decorative use only
  4. Create a secondary "accessible" set of your colors that meet AA even for small text

WCAG 3.0 and APCA

The next version of WCAG introduces the Advanced Perceptual Contrast Algorithm (APCA), which better accounts for font size, weight, and spatial frequency. It uses a different scale (Lc values instead of ratios) and gives more accurate results for non-black-on-white combinations like blue on dark gray. APCA is not yet a formal standard but is worth monitoring for future-proofing.

Try it yourself

Check color contrast ratios against WCAG AA and AAA standards instantly in your browser.

Open Contrast Checker →