Sprawdzanie kontrastu kolorów
Sprawdź stosunek kontrastu WCAG AA i AAA między kolorem tekstu a kolorem tła
Kolor pierwszego planu (tekstu)
Kolor tła
Przykład dużego tekstu (18px pogrubiony)
Przykład tekstu normalnego — tak będzie wyglądał twój tekst główny na wybranym kolorze tła.
Stosunek kontrastu
14.63:1
Zgodność z WCAG
Spełnia
Normalny AA
Spełnia
Normalny AAA
Spełnia
Duży AA
Spełnia
Duży AAA
WCAG AA — Tekst normalny: 4.5:1, Tekst duży (18px+ lub 14px pogrubiony): 3:1
WCAG AAA — Tekst normalny: 7:1, Tekst duży (18px+ lub 14px pogrubiony): 4.5:1
Czym jest sprawdzanie kontrastu kolorów?
Sprawdzanie kontrastu kolorów mierzy różnicę luminancji między kolorem pierwszego planu (zazwyczaj tekstu) a kolorem tła, a wynik wyraża jako stosunek. Stosunek 1:1 oznacza, że kolory są identyczne; 21:1 to wartość maksymalna, reprezentująca czarny na białym lub odwrotnie. Web Content Accessibility Guidelines (WCAG) opublikowane przez W3C definiują minimalne stosunki kontrastu, które tekst musi spełniać, aby osoby słabowidzące lub z zaburzeniami widzenia kolorów mogły go odczytać.
Wzór na stosunek kontrastu pochodzi z WCAG 2.x i opiera się na luminancji względnej — mierze tego, jak jasny kolor wydaje się ludzkiemu oku. Luminancja względna jest obliczana przez linearyzację każdego kanału sRGB (usunięcie korekcji gamma) i ważenie kanałów zgodnie ze współczynnikami ITU-R BT.709: 0,2126 dla czerwonego, 0,7152 dla zielonego i 0,0722 dla niebieskiego. Zielony wnosi największy wkład, ponieważ ludzkie oko jest najbardziej wrażliwe na zielone światło. Stosunek wynosi (L1 + 0,05) / (L2 + 0,05), gdzie L1 to luminancja jaśniejszego koloru.
WCAG definiuje dwa poziomy zgodności. Poziom AA wymaga stosunku kontrastu co najmniej 4,5:1 dla tekstu normalnego i 3:1 dla tekstu dużego (18px lub więcej, lub 14px pogrubiony). Poziom AAA podnosi poprzeczkę odpowiednio do 7:1 i 4,5:1. WCAG 2.1 wprowadził również kryterium sukcesu 1.4.11, które wymaga stosunku 3:1 dla nieodwzorowujących elementów interfejsu, takich jak obramowania, ikony i wskaźniki fokusu.
Dlaczego warto użyć tego narzędzia do sprawdzania kontrastu?
Ocenianie kontrastu na oko jest zawodne. Kolory, które wyglądają wyraźnie na skalibrowanym monitorze, mogą zlewać się na ekranie laptopa niskiej jakości, w bezpośrednim świetle słonecznym lub dla osoby z deuteranopią. Numeryczny stosunek eliminuje zgadywanie i daje jednoznaczny werdykt zaliczone/niezaliczone względem standardu WCAG.
Zastosowania narzędzia do sprawdzania kontrastu
Wymagania dotyczące stosunku kontrastu WCAG
Poniższa tabela podsumowuje minimalne stosunki kontrastu wymagane przez WCAG 2.1 dla różnych typów treści i poziomów zgodności. Tekst duży jest definiowany jako 18px (24 piksele CSS) lub więcej przy normalnej grubości, lub 14px (18,66 pikseli CSS) lub więcej przy grubości pogrubionej.
| Poziom | Min. stosunek | Dotyczy | Uwaga |
|---|---|---|---|
| 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 |
Luminancja, AA i AAA — wyjaśnienie
Obliczanie stosunku kontrastu składa się z trzech etapów: obliczenia luminancji dla każdego koloru, wyprowadzenia stosunku i porównania go z progami WCAG.
Przykłady kodu
Oblicz stosunki kontrastu WCAG programowo. Każdy przykład implementuje wzór luminancji względnej z WCAG 2.x oraz obliczanie stosunku kontrastu. Te same pary: czarny na białym i indygo na białym są testowane w celach porównawczych.
// 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;
}