Color Contrast Checker
Check WCAG AA and AAA contrast ratio between foreground and background colors
Foreground (text) color
Background color
Large Text Sample (18px bold)
Normal text sample β this is how your body text will appear against the background color you have chosen.
Contrast ratio
14.63:1
WCAG compliance
Pass
Normal AA
Pass
Normal AAA
Pass
Large AA
Pass
Large AAA
WCAG AA β Normal text: 4.5:1, Large text (18px+ or 14px bold): 3:1
WCAG AAA β Normal text: 7:1, Large text (18px+ or 14px bold): 4.5:1
What Is Color Contrast Checking?
Color contrast checking measures the luminance difference between a foreground color (typically text) and a background color, then expresses the result as a ratio. A ratio of 1:1 means the colors are identical; 21:1 is the maximum, representing black on white or vice versa. The Web Content Accessibility Guidelines (WCAG) published by the W3C define minimum contrast ratios that text must meet so that people with low vision or color deficiencies can read it.
The contrast ratio formula comes from WCAG 2.x and relies on relative luminance, a measure of how bright a color appears to the human eye. Relative luminance is calculated by linearizing each sRGB channel (removing gamma) and weighting the channels according to ITU-R BT.709 coefficients: 0.2126 for red, 0.7152 for green, and 0.0722 for blue. Green contributes the most because human eyes are most sensitive to green light. The ratio is then (L1 + 0.05) / (L2 + 0.05), where L1 is the lighter color's luminance.
WCAG defines two conformance levels. Level AA requires a contrast ratio of at least 4.5:1 for normal-sized text and 3:1 for large text (18px or above, or 14px bold). Level AAA raises the bar to 7:1 and 4.5:1 respectively. WCAG 2.1 also introduced Success Criterion 1.4.11, which requires a 3:1 ratio for non-text UI components like borders, icons, and focus indicators.
Why Use This Contrast Checker?
Checking contrast by eye is unreliable. Colors that look distinct on your calibrated display may blur together on a low-quality laptop screen, under direct sunlight, or for someone with deuteranopia. A numeric ratio removes guesswork and gives you a pass/fail verdict against the WCAG standard.
Contrast Checker Use Cases
WCAG Contrast Ratio Requirements
The table below summarizes the minimum contrast ratios required by WCAG 2.1 for different content types and conformance levels. Large text is defined as 18px (24 CSS pixels) or above at normal weight, or 14px (18.66 CSS pixels) or above at bold weight.
| Level | Min ratio | Applies to | Note |
|---|---|---|---|
| AA Normal text | 4.5 : 1 | Body text, paragraphs, labels | Minimum for most UI text |
| AA Large text | 3.0 : 1 | Text >= 18px, or >= 14px bold | Minimum for headings |
| AA UI components | 3.0 : 1 | Borders, icons, focus indicators | Non-text contrast (WCAG 1.4.11) |
| AAA Normal text | 7.0 : 1 | Body text at highest standard | Enhanced readability |
| AAA Large text | 4.5 : 1 | Large text at highest standard | Enhanced for headings |
Luminance, AA, and AAA Explained
The contrast ratio calculation has three stages: compute luminance for each color, derive the ratio, and compare it against WCAG thresholds.
Code Examples
Calculate WCAG contrast ratios programmatically. Each example implements the relative luminance formula from WCAG 2.x and the contrast ratio calculation. The same black-on-white and indigo-on-white pairs are tested for comparison.
// Calculate relative luminance per WCAG 2.x (sRGB)
function luminance(r, g, b) {
const [rs, gs, bs] = [r, g, b].map(c => {
c /= 255
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
})
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs
}
// Contrast ratio between two RGB colors
function contrastRatio(fg, bg) {
const l1 = luminance(...fg)
const l2 = luminance(...bg)
const lighter = Math.max(l1, l2)
const darker = Math.min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
}
contrastRatio([0, 0, 0], [255, 255, 255]) // -> 21.0
contrastRatio([99, 102, 241], [255, 255, 255]) // -> 3.95
contrastRatio([29, 78, 216], [255, 255, 255]) // -> 6.06 (AA pass)def luminance(r: int, g: int, b: int) -> float:
"""Relative luminance per WCAG 2.x, ITU-R BT.709 coefficients."""
channels = []
for c in (r, g, b):
c /= 255
channels.append(c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4)
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2]
def contrast_ratio(fg: tuple, bg: tuple) -> float:
l1 = luminance(*fg)
l2 = luminance(*bg)
lighter, darker = max(l1, l2), min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
print(f"{contrast_ratio((0, 0, 0), (255, 255, 255)):.2f}") # -> 21.00
print(f"{contrast_ratio((99, 102, 241), (255, 255, 255)):.2f}") # -> 3.95
# Check WCAG AA for normal text
ratio = contrast_ratio((29, 78, 216), (255, 255, 255))
print(f"{ratio:.2f} β {'AA pass' if ratio >= 4.5 else 'AA fail'}") # -> 6.06 β AA passpackage main
import (
"fmt"
"math"
)
func linearize(c float64) float64 {
c /= 255
if c <= 0.04045 {
return c / 12.92
}
return math.Pow((c+0.055)/1.055, 2.4)
}
func luminance(r, g, b int) float64 {
return 0.2126*linearize(float64(r)) +
0.7152*linearize(float64(g)) +
0.0722*linearize(float64(b))
}
func contrastRatio(fgR, fgG, fgB, bgR, bgG, bgB int) float64 {
l1 := luminance(fgR, fgG, fgB)
l2 := luminance(bgR, bgG, bgB)
lighter := math.Max(l1, l2)
darker := math.Min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
}
func main() {
ratio := contrastRatio(0, 0, 0, 255, 255, 255)
fmt.Printf("%.2f\n", ratio) // -> 21.00
ratio = contrastRatio(29, 78, 216, 255, 255, 255)
fmt.Printf("%.2f\n", ratio) // -> 6.06 (AA pass for normal text)
}/* WCAG-safe color pairs β tested contrast ratios */
/* 12.63:1 β passes AAA normal text */
.high-contrast {
color: #1e293b; /* slate-800 */
background: #f8fafc; /* slate-50 */
}
/* 7.07:1 β passes AAA normal text */
.dark-theme-text {
color: #e2e8f0; /* slate-200 */
background: #0f172a; /* slate-900 */
}
/* 4.57:1 β passes AA normal, fails AAA */
.accent-on-white {
color: #1d4ed8; /* blue-700 */
background: #ffffff;
}
/* 2.14:1 β fails AA for text, but passes 3:1 for large text */
.muted-heading {
color: #94a3b8; /* slate-400 */
background: #ffffff;
}