Rapporto di contrasto: come lo calcola davvero la WCAG
Pubblicato il 15/05/2025 · 16 min di lettura · Strumenti per sviluppatori
Daniel Okonkwo — Sviluppatore front-end e redattore Tech presso OneKitly
Performance web · Formati di file
Verificato su 6 fonti
La WCAG 2.x definisce il contrasto come (L1 + 0,05) ÷ (L2 + 0,05), dove L1 è la luminanza relativa del colore più chiaro e L2 quella del più scuro. La luminanza relativa non è la luminosità né la L di HSL. Ogni canale sRGB viene diviso per 255 e poi linearizzato con una funzione a tratti — sotto 0,03928 si divide per 12,92, altrimenti si eleva ((c + 0,055) ÷ 1,055) alla 2,4 — e i tre valori lineari vengono pesati 0,2126 rosso, 0,7152 verde, 0,0722 blu. Il verde porta il 71,52 % della luminanza e il blu solo il 7,22 %, il che produce il risultato che nessuno si aspetta: il blu puro #0000ff su bianco fa 8,59:1 e supera ogni livello WCAG, mentre il grigio medio #808080 su bianco fa 3,95:1 e fallisce per il testo corrente. Il termine 0,05 è il motivo per cui il nero su bianco vale 21:1 e non infinito: il nero ha luminanza 0, e 1,05 ÷ 0,05 = 21. Le soglie sono 4,5:1 per il testo di corpo in AA, 3:1 per il testo grande (18 pt = 24 px CSS, o 14 pt grassetto = 18,67 px) e per gli elementi non testuali, e 7:1 in AAA. La WCAG 3 è una bozza di lavoro iniziale che propone un altro modello, percettivo, chiamato APCA; oggi non è normativo.
Il rapporto WCAG è (L1 + 0,05) ÷ (L2 + 0,05), e L è la luminanza relativa, non la luminosità. Il verde ne porta il 71,52 % e il blu il 7,22 %, ecco perché il blu puro su bianco passa a 8,59:1 mentre il grigio medio fallisce a 3,95:1. Ecco l'intero calcolo, eseguito dall'inizio alla fine.
La formula: due luminanze e una costante
Tutto il contrasto della WCAG 2.x sta in una riga: rapporto = (L1 + 0,05) ÷ (L2 + 0,05), dove L1 è la luminanza relativa del più chiaro dei due colori e L2 quella del più scuro. Poiché la specifica li ordina per luminanza e non per ruolo, il rapporto non dipende mai da quale colore hai chiamato primo piano. Testo nero su bianco e testo bianco su nero danno lo stesso 21:1.
Tutto ciò che conta è nascosto dentro L. La luminanza relativa è una grandezza fisica: stima quanta luce emette la superficie, normalizzata perché il bianco puro valga 1 e il nero puro 0. Deliberatamente non è una misura di quanto chiaro appare il colore all'occhio umano, e non è di certo la L di HSL né la L di CIELAB, entrambe percettive ed entrambe con numeri molto diversi per lo stesso codice esadecimale.
Quella sola distinzione spiega quasi ogni discussione fra un designer e un verificatore di contrasto. Lo strumento non giudica il progetto: divide due numeri di un modello colorimetrico standardizzato ai tempi dei tubi catodici, che non ha mai preteso di coincidere con la percezione.
Passo uno: linearizzare ogni canale
Un valore sRGB di 128 non porta metà della luce di 255. Le codifiche di visualizzazione sono compresse in gamma per spendere il limitato intervallo a 8 bit dove la visione umana è sensibile: il numero memorizzato è quindi una funzione non lineare della luce. Prima di pesare o sommare qualsiasi cosa, ogni canale va riportato a una grandezza lineare.
La WCAG scrive la funzione di trasferimento per esteso. Dividi il valore a 8 bit per 255 per ottenere c in [0, 1]. Se c è al massimo 0,03928, il valore lineare è c ÷ 12,92. Altrimenti è ((c + 0,055) ÷ 1,055) elevato a 2,4. Il breve tratto lineare vicino al nero esiste perché una pura funzione potenza ha pendenza infinita a zero, cosa numericamente scomoda e fisicamente falsa per uno schermo reale: sRGB innesta lì una retta.
Eseguila e la non linearità è evidente: il canale 128 si linearizza a 0,215861, non a 0,5. Il canale 10 dà 0,003035 e il canale 11 dà 0,003347. C'è qui una nota che vale la pena conoscere, perché ricorre nelle segnalazioni di bug. La WCAG scrive la soglia come 0,03928 mentre la norma sRGB dell'IEC scrive 0,04045. In 8 bit tagliano rispettivamente a 10,0164 e 10,31, quindi entrambe cadono fra gli interi 10 e 11 e nessuna seleziona mai un ramo diverso. Una scansione di tutti i 16 777 216 colori sRGB contro il bianco ha prodotto zero cambi di verdetto e una differenza massima di rapporto esattamente nulla. La discrepanza è reale nel testo e non incide su alcun risultato a 8 bit.
Passo due: i pesi, dove vince il verde
I tre canali lineari si combinano come L = 0,2126 R + 0,7152 V + 0,0722 B. Quei coefficienti sono i pesi di luminanza della Rec. 709 e sommano esattamente a 1, il che porta il bianco a 1,000000. Codificano un fatto sulla retina umana, non sullo schermo: i coni che dominano la percezione della luminanza sono molto più sensibili al centro dello spettro visibile che alle sue estremità.
Le conseguenze sono nette appena si calcolano le primarie. Il rosso puro #ff0000 ha luminanza relativa 0,212600. Il verde puro #00ff00 ha 0,715200. Il blu puro #0000ff ha 0,072200. A parità di intensità di canale, il verde vale quasi dieci volte la luminanza del blu. Che un colore superi un controllo di contrasto dipende quindi molto più dal suo contenuto di verde che da ciò che un designer percepisce come la sua oscurità.
Una verifica utile: cerca il grigio con la stessa luminanza relativa del blu puro. È #4c4c4c, a 0,072272 contro 0,072200 del blu. Entrambi ottengono 8,59:1 su bianco. Per il modello quei due colori sono intercambiabili ai fini del contrasto, cosa che visibilmente non sono per un lettore — e proprio quel divario è la critica mossa alla WCAG 2 da chi ne costruisce il successore.
Il blu puro passa, il grigio medio fallisce
Passa entrambi nella formula. Blu puro su bianco: L1 = 1,000000, L2 = 0,072200, rapporto = 1,05 ÷ 0,122200 = 8,59:1. Supera l'AA per il testo di corpo a 4,5:1 e l'AAA a 7:1. Grigio medio #808080 su bianco: L2 = 0,215861, rapporto = 1,05 ÷ 0,265861 = 3,95:1, che fallisce nettamente l'AA per il testo di corpo e supera appena l'asticella di 3:1 del testo grande. La maggior parte delle persone, se deve indovinare, mette quei due colori nell'ordine opposto.
La stessa pesatura spiega il verde puro. #00ff00 su bianco fa 1,37:1 — peggio di quasi ogni colore che un designer prenderebbe in considerazione — perché il verde è il 71,52 % della luminanza e un verde pieno è quindi quasi luminoso come il bianco. Su nero, lo stesso verde fa 15,30:1. Il blu puro su nero fa solo 2,44:1 e fallisce persino la soglia non testuale di 3:1, l'esatto inverso del suo ottimo risultato su bianco.
Per i grigi la frontiera merita di essere memorizzata. Scorrendo la rampa neutra contro il bianco, #767676 dà 4,5422:1 e supera l'AA, mentre il valore immediatamente successivo #777777 dà 4,4781:1 e fallisce. #767676 è quindi il grigio più chiaro capace di portare testo di corpo su fondo bianco, e #595959 cade su 7,00:1, esattamente la linea AAA.
A che cosa serve lo 0,05
Il nero ha luminanza relativa 0. Dividere qualsiasi cosa per 0 dà un rapporto infinito, inutile come punteggio e falso come descrizione di uno schermo reale. Lo 0,05 aggiunto a entrambi i termini rappresenta la luce ambientale riflessa dalla superficie dello schermo: un pixel nero non è mai davvero nero in una stanza illuminata, e la costante approssima il velo che lo solleva.
L'aritmetica è immediata. Il bianco su nero diventa (1 + 0,05) ÷ (0 + 0,05) = 1,05 ÷ 0,05 = 21. È da lì che viene il famoso tetto di 21:1 — non è una decisione di progetto, è 1,05 diviso 0,05. Togli la costante e la stessa coppia si valuta a infinito, che è ciò che restituisce un'implementazione ingenua e ciò che diverse prime librerie di contrasto hanno effettivamente pubblicato.
La costante comprime anche l'estremità scura della scala. Due colori quasi neri la cui luminanza differisce di un fattore dieci hanno rapporti di contrasto quasi identici, perché 0,05 domina entrambi i denominatori. È voluto — a quei livelli la differenza è davvero invisibile con luce normale — ma spiega perché le palette in modalità scura sembrano più difficili da regolare: la formula smette di discriminare proprio dove vivono i temi scuri.
Le soglie e che cosa significa testo grande in cifre
Il criterio 1.4.3 (Contrasto minimo, livello AA) richiede 4,5:1 per testo e immagini di testo, sceso a 3:1 per il testo grande. Il criterio 1.4.6 (Contrasto migliorato, livello AAA) porta quei valori a 7:1 e 4,5:1. Il criterio 1.4.11 (Contrasto non testuale, livello AA) richiede 3:1 per componenti di interfaccia e oggetti grafici necessari alla comprensione — bordi di modulo, indicatori di focus, glifi di icone, segmenti di grafici.
Il testo grande è definito in punti, non in pixel: almeno 18 punti, o almeno 14 punti se in grassetto. Convertendo con il riferimento CSS di 1 pt = 4/3 px si ottengono 24 px e 18,67 px in grassetto. Sono questi i numeri da controllare in un foglio di stile. Un titolo da 20 px non è testo grande secondo questa definizione e richiede ancora 4,5:1, cosa che coglie moltissimi progetti che davano i titoli per esenti.
Due esenzioni vale la pena conoscerle, perché vengono spesso lette male. Il testo che fa parte di un componente di interfaccia inattivo, la pura decorazione o ciò che è invisibile a tutti non hanno requisito di contrasto. E i logotipi — testo che fa parte di un logo o di un nome di marca — sono anch'essi esenti. Nessuna di queste esenzioni copre un segnaposto poco contrastato in un campo di modulo né un'etichetta sbiadita che gli utenti devono comunque leggere.
Dove si collocano APCA e WCAG 3
Il risultato grigio-blu qui sopra non è un errore di implementazione: è un limite del modello. Un rapporto di luminanza ignora tinta, saturazione, peso del carattere, spessore del tratto e quale dei due colori stia sopra — tutti fattori che incidono in modo misurabile sulla leggibilità. Un carattere sottile di peso 400 a 4,5:1 può essere più difficile da leggere di uno pesante a 4:1, e la formula non può dirlo.
APCA, l'Accessible Perceptual Contrast Algorithm, è la risposta. Modella la chiarezza percepita anziché la luminanza fisica, tiene deliberatamente conto della polarità — scuro su chiaro e chiaro su scuro danno punteggi diversi per la stessa coppia — e produce un valore con segno su una scala di circa meno 108 a più 106 anziché un rapporto, con l'obiettivo che dipende congiuntamente da dimensione e peso del carattere.
Il suo stato va enunciato con cura. WCAG 3 è una bozza di lavoro del W3C — un documento iniziale, esplicitamente instabile, che il W3C stesso avverte di non usare come base per implementare. APCA vi figura come metodo candidato, non come requisito ratificato. Oggi nessuna valutazione legale o contrattuale avviene contro APCA. WCAG 2.2 è la raccomandazione W3C in vigore ed è ciò a cui politiche di accessibilità, appalti e contenziosi fanno davvero riferimento. Usa APCA come secondo parere quando il rapporto e i tuoi occhi non concordano; pubblica contro i numeri della 2.x.
| Coppia | L più chiara | L più scura | Rapporto | AA corpo 4,5:1 | AA grande 3:1 | AAA corpo 7:1 |
|---|---|---|---|---|---|---|
| #000000 su #ffffff | 1,000000 | 0,000000 | 21,00:1 | Superato | Superato | Superato |
| #0000ff su #ffffff | 1,000000 | 0,072200 | 8,59:1 | Superato | Superato | Superato |
| #595959 su #ffffff | 1,000000 | 0,099899 | 7,00:1 | Superato | Superato | Superato (esattamente sulla linea) |
| #767676 su #ffffff | 1,000000 | 0,181164 | 4,54:1 | Superato (il grigio più chiaro che ci riesce) | Superato | Non superato |
| #777777 su #ffffff | 1,000000 | 0,184475 | 4,48:1 | Non superato (un passo più scuro supera) | Superato | Non superato |
| #ff0000 su #ffffff | 1,000000 | 0,212600 | 4,00:1 | Non superato | Superato | Non superato |
| #808080 su #ffffff | 1,000000 | 0,215861 | 3,95:1 | Non superato | Superato | Non superato |
| #00ff00 su #ffffff | 1,000000 | 0,715200 | 1,37:1 | Non superato | Non superato | Non superato |
Domande frequenti
- Perché il blu puro su bianco passa mentre il grigio medio fallisce?
- Perché la formula pesa il verde a 0,7152 e il blu solo a 0,0722. Il blu puro non ha né rosso né verde, quindi la sua luminanza relativa è 0,072200 — molto scuro secondo il modello, anche se appare vivido. Il grigio medio #808080 ha quantità uguali dei tre canali e, dopo la linearizzazione, la sua luminanza è 0,215861, tre volte tanto. Contro il bianco ciò dà 8,59:1 per il blu e 3,95:1 per il grigio: il blu supera AA e AAA mentre il grigio fallisce il testo di corpo AA. Nulla è andato storto: la luminanza relativa misura emissione di luce, non quanto una cosa appaia colorata o scura. La lezione pratica è smettere di indovinare. Blu, viola e rossi profondi saturi ottengono regolarmente punteggi migliori di quanto il loro aspetto suggerisca, e gialli, ciani e verdi chiari molto peggiori. Con una palette di marca molto colorata, misura ogni coppia invece di giudicare a occhio: l'intuizione su ciò che è scuro è addestrata sulla chiarezza percepita, e la formula no.
- Il rapporto cambia se scambio il colore di primo piano con quello di sfondo?
- No. La WCAG 2.x definisce L1 come la luminanza relativa del colore più chiaro e L2 come quella del più scuro, quindi l'ordine è deciso dalle luminanze stesse, non da quale hai usato per il testo. Testo nero su bianco e testo bianco su nero danno entrambi 21:1. Testo blu #0000ff su bianco e testo bianco su blu danno entrambi 8,59:1. Quella simmetria è una proprietà reale del modello 2.x e un limite reale, perché la polarità incide eccome sulla leggibilità: il testo chiaro su fondo scuro tende ad apparire più sottile e può sfrangiare su alcuni schermi, un effetto che il rapporto non può rappresentare. APCA, l'algoritmo candidato nella bozza WCAG 3, rompe deliberatamente quella simmetria e riporta valori diversi a seconda dell'ordine. Sotto WCAG 2.x, invece, un verificatore che dia una risposta diversa quando scambi i campi ha un bug.
- Qual è il grigio più chiaro utilizzabile per il testo di corpo su bianco?
- #767676. Scorrendo la rampa neutra contro #ffffff, quel valore produce 4,5422:1 e supera la soglia AA di 4,5:1. Il passo successivo, #777777, produce 4,4781:1 e fallisce. Fra i due non esiste alcun grigio — sono valori a 8 bit adiacenti — quindi #767676 è esattamente la frontiera. Due conseguenze pratiche. Primo, se il tuo design system usa un token come grey-500 per il testo secondario, controlla il suo esadecimale reale: moltissimi sistemi finiscono fra 0x77 e 0x99 e non sono conformi per il testo di corpo, pur sembrando del tutto deliberati. Secondo, se ti serve AAA, la frontiera equivalente è #595959, che dà esattamente 7,00:1. Entrambe le cifre presuppongono uno sfondo bianco puro; su una superficie bianco sporco come #fafafa l'intera scala si sposta e occorre ricalcolare. È a questo che serve il verificatore di contrasto: la frontiera si muove con lo sfondo, e i valori memorizzati smettono di essere veri appena lo sfondo cambia.
- Anche icone, bordi di modulo e anelli di focus richiedono 4,5:1?
- No — ricadono sotto il criterio 1.4.11, Contrasto non testuale, che chiede 3:1 al livello AA. Si applica a due cose: l'informazione visiva necessaria per identificare un componente di interfaccia e il suo stato, e gli oggetti grafici necessari a comprendere il contenuto. Quindi il bordo che dice dove inizia un campo di testo richiede 3:1 rispetto a ciò che lo circonda, così come un indicatore di focus, una casella spuntata, un interruttore in posizione attiva e i segmenti di colore diverso di un grafico che il lettore deve distinguere. Ciò che non copre: la decorazione, lo stato inattivo di un componente, o le grafiche in cui una presentazione specifica è essenziale — una fotografia, una bandiera, una schermata di un altro prodotto. Nota che il confronto è con i colori adiacenti, il che per un anello di focus significa lo sfondo su cui poggia e per un'icona su un pulsante colorato significa il riempimento del pulsante, non la pagina. Questo solo criterio scova più difetti reali nelle librerie di componenti moderne di quanti ne scovino le regole sul testo, perché i campi disegnati con un bordo grigio chiaro da 1 px sono quasi universali.
- APCA sostituisce la regola del 4,5:1, e devo passarci ora?
- Non ancora, e forse non in quella forma. WCAG 3 è una bozza di lavoro del W3C, uno stato che significa esplicitamente instabile e inadatto come base di implementazione; il testo stesso avverte di non citarla come qualcosa di stabilito. APCA vi compare come metodo candidato in valutazione, insieme a domande aperte sulle soglie e su come si punteggerebbe la conformità. Nel frattempo WCAG 2.2 è una raccomandazione del W3C, ed è a WCAG 2.x che puntano le legislazioni sull'accessibilità, le regole di appalto pubblico e i reclami legali in tutto il mondo. Oggi nulla viene valutato contro APCA. La posizione sensata è continuare a conformarsi alla 2.x — 4,5:1, 3:1, 7:1, calcolati come sopra — e trattare APCA come diagnosi quando il rapporto e i tuoi occhi non concordano, cosa che accade soprattutto con tinte sature e con caratteri sottili su fondi scuri. Se APCA dice che una coppia è peggiore di quanto il rapporto suggerisca, vale la pena intervenire; se dice che è migliore, non puoi comunque pubblicare sotto 4,5:1 e dichiarare AA.
- Come calcolo a mano il rapporto per un colore specifico?
- Prendi #1a73e8 su bianco come esempio svolto. Dividi l'esadecimale in canali: 26, 115, 232. Dividi ciascuno per 255: 0,101961, 0,450980, 0,909804. Tutti e tre superano 0,03928, quindi tutti e tre usano il ramo di potenza: ((c + 0,055) ÷ 1,055) alla 2,4, dando 0,010330, 0,171441, 0,806952. Applica i pesi: 0,2126 × 0,010330 = 0,002196, 0,7152 × 0,171441 = 0,122615, 0,0722 × 0,806952 = 0,058262. Sommali per L = 0,183073. Il bianco è 1,000000 ed è il più chiaro, quindi il rapporto è (1 + 0,05) ÷ (0,183073 + 0,05) = 1,05 ÷ 0,233073 = 4,51:1. Supera l'AA per il testo di corpo di sei centesimi, cosa che merita attenzione: un colore così vicino alla linea fallirà appena qualcuno lo schiarisce, quindi tratta la fascia 4,5-4,6 come zona di allerta e non come promozione. L'intera procedura sta in dodici righe di codice in qualsiasi linguaggio, ed è per questo che tutti i verificatori concordano fino al secondo decimale — l'unica vera fonte di disaccordo fra strumenti è l'arrotondamento.
Articoli che potrebbero interessarti
Tutte le guide →Strumenti correlati
Fonti
- W3C — Web Content Accessibility Guidelines (WCAG) 2.2 — relative luminance and contrast ratio definitions
- W3C WAI — Understanding Success Criterion 1.4.3: Contrast (Minimum)
- W3C WAI — Understanding Success Criterion 1.4.6: Contrast (Enhanced)
- W3C WAI — Understanding Success Criterion 1.4.11: Non-text Contrast
- W3C — W3C Accessibility Guidelines (WCAG) 3.0 — Working Draft (APCA appears here as a candidate method)
- IEC — IEC 61966-2-1:1999 — Default RGB colour space (sRGB), source of the piecewise transfer function
Hai notato un errore in questo articolo?