Verifica Contrasto Colori
Controlla il rapporto di contrasto WCAG AA e AAA tra colori in primo piano e di sfondo
Colore in primo piano (testo)
Colore di sfondo
Testo grande di esempio (18px grassetto)
Esempio di testo normale — ecco come apparirà il corpo del testo rispetto al colore di sfondo scelto.
Rapporto di contrasto
14.63:1
Conformità WCAG
Superato
Normale AA
Superato
Normale AAA
Superato
Grande AA
Superato
Grande AAA
WCAG AA — Testo normale: 4.5:1, Testo grande (18px+ o 14px grassetto): 3:1
WCAG AAA — Testo normale: 7:1, Testo grande (18px+ o 14px grassetto): 4.5:1
Cos'è la verifica del contrasto dei colori?
La verifica del contrasto dei colori misura la differenza di luminanza tra un colore in primo piano (tipicamente il testo) e un colore di sfondo, esprimendo il risultato come rapporto. Un rapporto di 1:1 significa che i colori sono identici; 21:1 è il massimo, che rappresenta il nero su bianco o viceversa. Le Web Content Accessibility Guidelines (WCAG), pubblicate dal W3C, definiscono i rapporti minimi di contrasto che il testo deve rispettare affinché le persone con ipovisione o deficit di percezione dei colori possano leggerlo.
La formula del rapporto di contrasto proviene da WCAG 2.x e si basa sulla luminanza relativa, una misura della luminosità apparente di un colore all'occhio umano. La luminanza relativa viene calcolata linearizzando ogni canale sRGB (rimuovendo la gamma) e ponderando i canali secondo i coefficienti ITU-R BT.709: 0,2126 per il rosso, 0,7152 per il verde e 0,0722 per il blu. Il verde contribuisce maggiormente perché l'occhio umano è più sensibile alla luce verde. Il rapporto è quindi (L1 + 0,05) / (L2 + 0,05), dove L1 è la luminanza del colore più chiaro.
WCAG definisce due livelli di conformità. Il livello AA richiede un rapporto di contrasto di almeno 4,5:1 per il testo di dimensioni normali e di 3:1 per il testo grande (18px o superiore, o 14px in grassetto). Il livello AAA innalza la soglia a 7:1 e 4,5:1 rispettivamente. WCAG 2.1 ha inoltre introdotto il Criterio di successo 1.4.11, che richiede un rapporto di 3:1 per i componenti UI non testuali come bordi, icone e indicatori di focus.
Perché usare questo verificatore di contrasto?
Valutare il contrasto a occhio non è affidabile. Colori che appaiono distinti sul tuo schermo calibrato possono confondersi su uno schermo di bassa qualità, alla luce diretta del sole, o per chi soffre di deuteranopia. Un rapporto numerico elimina le congetture e fornisce un verdetto superato/non superato rispetto allo standard WCAG.
Casi d'uso del verificatore di contrasto
Requisiti di rapporto di contrasto WCAG
La tabella seguente riassume i rapporti minimi di contrasto richiesti da WCAG 2.1 per diversi tipi di contenuto e livelli di conformità. Il testo grande è definito come 18px (24 pixel CSS) o superiore a peso normale, o 14px (18,66 pixel CSS) o superiore a peso grassetto.
| Livello | Rapporto minimo | Si applica a | Nota |
|---|---|---|---|
| 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 |
Luminanza, AA e AAA spiegati
Il calcolo del rapporto di contrasto si articola in tre fasi: calcolare la luminanza per ciascun colore, ricavare il rapporto e confrontarlo con le soglie WCAG.
Esempi di codice
Calcola i rapporti di contrasto WCAG a livello di codice. Ogni esempio implementa la formula della luminanza relativa da WCAG 2.x e il calcolo del rapporto di contrasto. Le stesse coppie nero su bianco e indigo su bianco vengono testate per confronto.
// 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;
}