Color Contrast — Complete Guide
In this tutorial, you will learn about Color Contrast. We cover key concepts, practical examples, and best practices to help you master this topic.
Color contrast measures the luminance difference between text and background colors, with WCAG requiring minimum contrast ratios of 4.5:1 for normal text and 3:1 for large text to ensure readability for users with low vision.
What You'll Learn
- How contrast ratio is calculated
- WCAG contrast requirements for AA and AAA
- How to choose accessible color palettes
- Tools for testing contrast ratios
- Contrast considerations for non-text elements
Why It Matters
- Over 250 million people have moderate to severe visual impairments
- Poor contrast affects all users in bright environments or with low-quality displays
- WCAG 2.2 requires specific contrast ratios for text and non-text content
- Low contrast is one of the most common Accessibility failures found in audits
Real-World Use
- A banking app ensures all Transaction amounts meet AAA contrast for critical information
- A news website uses AA contrast for body text and AAA for financial data
- A dashboard uses colorblind-safe palettes for charts and graphs
- A form uses high-contrast error messages that are visible even in sunlight
flowchart LR
A[Choose Text Color] --> B[Choose Background Color]
B --> C[Calculate Contrast Ratio]
C --> D{Ratio >= 4.5:1?}
D -->|Yes| E[AA Pass]
D -->|No| F[Adjust Colors]
F --> A
E --> G{Need AAA?}
G -->|Yes| H[Ratio >= 7:1]
G -->|No| I[Ready]
Understanding Color Contrast
Color contrast is the difference in luminance between two adjacent colors. The higher the contrast, the easier it is to distinguish text from its background.
Contrast ratio is calculated using the relative luminance of each color, ranging from 1:1 (same color, no contrast) to 21:1 (black on white, maximum contrast). The formula involves the sRGB color space and the WCAG 2.x relative luminance calculation.
WCAG Requirements
Level AA (minimum standard):
- Normal text (under 18pt / 24px): 4.5:1
- Large text (18pt / 24px and above, or 14pt bold): 3:1
- Non-text content (UI components, graphics): 3:1
Level AAA (enhanced):
- Normal text: 7:1
- Large text: 4.5:1
Why These Ratios Matter
The 4.5:1 ratio was chosen because it compensates for the loss of contrast sensitivity experienced by users with approximately 20/40 vision, which is typical for people in their 80s. At 3:1, text is readable for most users with normal vision but becomes difficult for many with visual impairments.
Code Example: Accessible Color Palette
/* Accessible color palette with contrast ratios checked */
:root {
/* Primary colors - all pass AA on white background */
--primary-dark: #0056B3; /* 5.7:1 on white - AA */
--primary-main: #0066CC; /* 4.6:1 on white - AA */
--primary-light: #4D94FF; /* 2.9:1 on white - fails AA */
/* Text colors */
--text-primary: #1A1A1A; /* 15.3:1 on white - AAA */
--text-secondary: #595959; /* 5.0:1 on white - AA */
--text-disabled: #A3A3A3; /* 2.6:1 on white - fails AA */
/* Background colors */
--bg-white: #FFFFFF;
--bg-light: #F5F5F5; /* Good for sections */
--bg-dark: #1A1A1A; /* For dark mode */
/* Success and error */
--success: #2E7D32; /* 5.6:1 on white - AA */
--error: #C62828; /* 5.9:1 on white - AA */
--warning: #F57F17; /* 3.2:1 on white - large text only */
}
/* Usage example */
body {
color: var(--text-primary);
background: var(--bg-white);
}
.secondary-text {
color: var(--text-secondary);
}
.error-message {
color: var(--error);
background: #FFEBEE;
padding: 0.5rem;
border-radius: 4px;
}
Expected output: All colors in the palette have been checked for WCAG Compliance. Primary-dark and primary-main pass AA on white backgrounds. Primary-light is used only for large decorative elements, not text.
Code Example: Contrast Checking Function
// Calculate relative luminance
function getLuminance(hex) {
const rgb = hexToRgb(hex);
const [r, g, b] = [rgb.r, rgb.g, rgb.b].map(c => {
const s = c / 255;
return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
});
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
// Calculate contrast ratio
function getContrastRatio(hex1, hex2) {
const l1 = getLuminance(hex1);
const l2 = getLuminance(hex2);
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
// Check WCAG compliance
function checkWCAG(foreground, background) {
const ratio = getContrastRatio(foreground, background);
console.log(`Ratio: ${ratio.toFixed(1)}:1`);
if (ratio >= 7) {
console.log('AAA Pass for all text sizes');
} else if (ratio >= 4.5) {
console.log('AA Pass for normal text');
console.log('AAA Pass for large text');
} else if (ratio >= 3) {
console.log('AA Pass for large text only');
console.log('FAIL for normal text');
} else {
console.log('FAIL: Does not meet any WCAG level');
}
}
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
};
}
// Test
checkWCAG('#333333', '#FFFFFF'); // Dark gray on white
checkWCAG('#CCCCCC', '#FFFFFF'); // Light gray on white
checkWCAG('#0066CC', '#FFFFFF'); // Blue on white
Expected output: The dark gray on white returns a ratio of approximately 12.6:1 (AAA). The light gray returns approximately 2.3:1 (fails). The blue returns 4.6:1 (AA).
Code Example: Non-text Contrast
<!-- Non-text elements must also meet 3:1 minimum -->
<style>
/* Icons must have 3:1 contrast against background */
.icon-success {
fill: #2E7D32; /* 5.6:1 on white - passes */
}
/* Focus indicator must have 3:1 */
:focus-visible {
outline: 3px solid #0056B3; /* 5.7:1 on white */
outline-offset: 2px;
}
/* Graph colors - colorblind safe */
.chart-bar-1 { fill: #0072B2; } /* Blue - distinguishable */
.chart-bar-2 { fill: #D55E00; } /* Orange - distinguishable */
.chart-bar-3 { fill: #009E73; } /* Green - distinguishable */
/* Input borders */
input:invalid {
border: 2px solid #C62828; /* Red, 5.9:1 on white */
}
/* Placeholder text exception - not required but recommended */
::placeholder {
color: #757575; /* 4.6:1 - AA compliant */
}
</style>
<div>
<span class="icon-success">
<svg width="24" height="24" viewBox="0 0 24 24">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
</svg>
</span>
<span>Payment successful</span>
</div>
Expected output: The success icon uses a color that contrasts sufficiently against the background. Focus indicators are visible. Chart colors are distinguishable for users with deuteranopia (red-green color blindness).
Common Mistakes
- Relying on color alone to convey information — A red error message without an icon or text description is invisible to colorblind users. Always pair color with text labels, icons, or patterns.
- Using brand colors that fail contrast — Many brand color palettes were designed without accessibility in mind. Create accessible variations or use brand colors only for large decorative elements.
- Ignoring contrast on hover states — A button that meets contrast in its default state but drops below the threshold on hover creates an inconsistent experience.
- Low contrast placeholder text — Placeholder text often uses light gray that fails WCAG. While placeholders are technically exempt, they should still be readable.
- Focusing only on text contrast — Non-text elements like icons, chart lines, focus indicators, and input borders also need minimum 3:1 contrast.
- Not testing in different lighting conditions — A design that looks fine in a dark office may be unreadable in direct sunlight. Test at various brightness levels.
- Using pure black on pure white — #000 on #FFF has 21:1 contrast but can cause eye strain for some readers. Dark gray on off-white is often more comfortable and still meets AAA.
Practice Questions
- What is the minimum contrast ratio required by WCAG AA for normal text? 4.5:1.
- What is the minimum contrast ratio for large text at WCAG AA? 3:1 (for text that is at least 18pt or 14pt bold).
- What is the difference between AA and AAA contrast requirements for normal text? AA requires 4.5:1, AAA requires 7:1.
- Do non-text elements need to meet contrast requirements? Yes, non-text elements like icons, charts, and focus indicators need a minimum 3:1 contrast ratio against adjacent colors.
- Challenge: Audit a website of your choice for color contrast violations. Use a tool like the WebAIM Contrast Checker. Find at least 5 violations and document the fix for each one, showing before and after hex values.
FAQ
Mini Project
Design an accessible color system for a dashboard application. Create a CSS custom property palette that includes: at least 3 neutral text colors (all passing AA on light and dark backgrounds), 3 semantic colors (success, warning, error) that pass AA on light backgrounds, a dark mode variant of all colors, focus indicator colors, and chart/visualization colors that are colorblind-safe. Document each color with its hex value, contrast ratio on both light and dark backgrounds, and WCAG compliance level. Test the palette with a colorblind simulator.
What's Next
Continue with Lesson 11: Non-Text Content to learn how to make images, icons, charts, and multimedia accessible to all users.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro