Introduction

Web Content Accessibility Guidelines (WCAG) ensure that websites are usable by people with disabilities, including visual impairments. Color contrast is one of the most important accessibility requirements.

What is Contrast Ratio?

Contrast ratio measures the difference in luminance between text and its background. It’s expressed as a ratio:

Formula:

Contrast Ratio = (L1 + 0.05) / (L2 + 0.05)

Where L1 is the relative luminance of the lighter color and L2 is the darker.

Scale: 1:1 (no contrast) to 21:1 (maximum contrast, white on black)

WCAG Requirements

Level AA (Standard)

Normal Text (under 18pt or 14pt bold):

  • Minimum: 4.5:1

Large Text (18pt+ or 14pt+ bold):

  • Minimum: 3:1

Level AAA (Enhanced)

Normal Text:

  • Minimum: 7:1

Large Text:

  • Minimum: 4.5:1

Examples

✅ Passes WCAG AA

ForegroundBackgroundRatioStatus
Black (#000)White (#fff)21:1✅ AAA
Dark Gray (#333)White (#fff)12.6:1✅ AAA
Navy (#003366)White (#fff)12.4:1✅ AAA
Medium Gray (#666)White (#fff)5.7:1✅ AA
Blue (#0066cc)White (#fff)4.5:1✅ AA (exact)

❌ Fails WCAG AA

ForegroundBackgroundRatioStatus
Light Gray (#ccc)White (#fff)1.6:1❌ Fails
Yellow (#ffff00)White (#fff)1.1:1❌ Fails
Light Blue (#66ccff)White (#fff)2.3:1❌ Fails
Orange (#ff9900)White (#fff)2.9:1❌ Fails

Calculating Contrast

Relative Luminance

First, calculate relative luminance for each color:

function getLuminance(hex) {
  const rgb = hexToRgb(hex);
  const [r, g, b] = rgb.map((val) => {
    val = val / 255;
    return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
  });
  return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}

function getContrastRatio(color1, color2) {
  const lum1 = getLuminance(color1);
  const lum2 = getLuminance(color2);
  const lighter = Math.max(lum1, lum2);
  const darker = Math.min(lum1, lum2);
  return (lighter + 0.05) / (darker + 0.05);
}

Common Issues

1. Light Gray Text on White

/* ❌ Poor contrast */
.text {
  color: #cccccc;
  background: #ffffff;
  /* Ratio: 1.6:1 - Fails! */
}

/* ✅ Good contrast */
.text {
  color: #666666;
  background: #ffffff;
  /* Ratio: 5.7:1 - Passes AA */
}

2. Colored Text on Similar Backgrounds

/* ❌ Poor contrast */
.button {
  color: #ffcc00;
  background: #ffffcc;
  /* Ratio: 1.4:1 - Fails! */
}

/* ✅ Good contrast */
.button {
  color: #996600;
  background: #ffffcc;
  /* Ratio: 7.2:1 - Passes AAA */
}

3. Transparent Overlays

/* ❌ Poor contrast with transparency */
.overlay {
  background: rgba(255, 255, 255, 0.3);
  color: white;
  /* Actual contrast depends on background */
}

/* ✅ Better - darker overlay */
.overlay {
  background: rgba(0, 0, 0, 0.5);
  color: white;
  /* Better contrast */
}

Best Practices

1. Default to High Contrast

Start with high-contrast combinations:

  • Dark text on light backgrounds
  • Light text on dark backgrounds
  • Avoid gray-on-gray combinations

2. Test All States

Check contrast for:

  • Normal state
  • Hover state
  • Focus state
  • Disabled state
  • Error states
.button {
  color: #ffffff;
  background: #0066cc;
  /* Ratio: 4.5:1 - Passes */
}

.button:hover {
  background: #0052a3;
  /* Ratio: 4.5:1 - Still passes */
}

.button:disabled {
  color: #cccccc;
  background: #e0e0e0;
  /* Ratio: 1.6:1 - Fails! */
}

3. Use Tools

Our tools:

Other tools:

  • WebAIM Contrast Checker
  • Chrome DevTools Lighthouse
  • axe DevTools

4. Consider Dark Mode

Dark mode needs separate contrast testing:

:root {
  --text: #333333;
  --bg: #ffffff;
}

@media (prefers-color-scheme: dark) {
  :root {
    --text: #e0e0e0;
    --bg: #1a1a1a;
  }
}

Fixing Low Contrast

Method 1: Darken Text

/* Before */
.text {
  color: #cccccc;
} /* 1.6:1 */

/* After */
.text {
  color: #666666;
} /* 5.7:1 */

Method 2: Lighten Background

/* Before */
.text {
  color: #333333;
  background: #f0f0f0;
} /* 10:1 */

/* Already good, but can improve */
.text {
  color: #333333;
  background: #ffffff;
} /* 12.6:1 */

Method 3: Add Outline/Shadow

/* Low contrast text */
.text {
  color: #ffcc00;
  background: #ffffcc;
  text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
  /* Shadow improves readability */
}

Method 4: Use Different Colors

/* Before - fails */
.link {
  color: #66ccff;
} /* 2.3:1 */

/* After - passes */
.link {
  color: #0066cc;
} /* 4.5:1 */

Real-World Examples

Accessible Button

.button-primary {
  background: #0066cc;
  color: #ffffff;
  /* Ratio: 4.5:1 - Passes AA */
  padding: 12px 24px;
  border-radius: 8px;
}

.button-primary:hover {
  background: #0052a3;
  /* Ratio: 4.5:1 - Maintains contrast */
}

.button-primary:focus {
  outline: 2px solid #0066cc;
  outline-offset: 2px;
}

Accessible Form Input

.input {
  border: 2px solid #666666;
  /* Ratio: 5.7:1 - Passes AA */
  color: #333333;
  background: #ffffff;
  padding: 8px 12px;
}

.input:focus {
  border-color: #0066cc;
  outline: 2px solid #0066cc;
}

.input-error {
  border-color: #cc0000;
  /* Error states need good contrast too */
}

Accessible Card

.card {
  background: #ffffff;
  border: 1px solid #e0e0e0;
}

.card-title {
  color: #333333;
  /* Ratio: 12.6:1 - Excellent */
}

.card-text {
  color: #666666;
  /* Ratio: 5.7:1 - Passes AA */
}

Testing Tools

Browser DevTools

Chrome DevTools:

  1. Inspect element
  2. Check Computed styles
  3. Use Lighthouse for accessibility audit

Automated Testing

// Using axe-core
import axe from "axe-core";

axe.run(document, (err, results) => {
  if (results.violations) {
    results.violations.forEach((violation) => {
      if (violation.id === "color-contrast") {
        console.error("Contrast violation:", violation);
      }
    });
  }
});

Many countries require WCAG compliance:

  • USA: Section 508, ADA
  • EU: EN 301 549, Web Accessibility Directive
  • Canada: ACA (Accessible Canada Act)
  • Australia: DDA (Disability Discrimination Act)

Benefits:

  • Legal compliance
  • Larger audience (15% of population has disabilities)
  • Better SEO
  • Improved UX for all users

Conclusion

Color contrast is essential for web accessibility:

Remember:

  • Minimum 4.5:1 for normal text (WCAG AA)
  • Minimum 3:1 for large text (WCAG AA)
  • Test all interactive states
  • Use tools to verify
  • Consider dark mode separately

Benefits:

  • Legal compliance
  • Better user experience
  • Improved SEO
  • Larger audience reach

Next Steps