Vai al contenuto
OneKitly

Gli schemi di colore sono geometria su una ruota — e la ruota è sbagliata

Pubblicato il 15/08/2025 · 16 min di lettura · Strumenti per sviluppatori

Daniel Okonkwo

Daniel OkonkwoSviluppatore front-end e redattore Tech presso OneKitly

Performance web · Formati di file

Verificato su 6 fonti

Vedi il profilo
In breve

Uno schema di colore è un angolo. Prendi la tinta di un colore base, aggiungi un numero fisso di gradi modulo 360 e hai lo schema: il complementare aggiunge 180°, la triade aggiunge 120° due volte, l'analogo aggiunge ±30°, il complementare diviso aggiunge 150° e 210°, il tetradico aggiunge 90° tre volte. Dalla tinta 210 questo dà 30 per il complementare, 330 e 90 per la triade, 180 e 240 per i vicini, 0 e 60 per il diviso. È tutto il calcolo, e ogni generatore lo esegue. Il guaio è il cerchio su cui ruota. L'asse delle tinte di HSL è una costruzione geometrica sul cubo RGB, non un modello della visione: angoli uguali non sono passi percettivi uguali, e una luminosità HSL uguale non è una chiarezza percepita uguale. Resi come hsl(H 100% 50%), il giallo ha una luminanza relativa WCAG di 0,9278 e il blu 0,0722 — la stessa luminosità nominale, 12,85 volte la luce. OKLCH e CIELAB risolvono definendo la chiarezza in modo percettivo: la stessa coppia è 0,968 contro 0,452 in OKLCH, un rapporto di 2,14. La conseguenza pratica è inevitabile: la rotazione non preserva nulla del contrasto, quindi una tavolozza generata va verificata contro i suoi sfondi prima di pubblicarla.

Complementare, triade, analogo e complementare diviso sono solo rotazioni: aggiungi 180°, 120°, 30° o 150° a una tinta. L'aritmetica è banale. Il problema è che il cerchio delle tinte HSL non è percettivamente uniforme — giallo e blu alla stessa luminosità HSL differiscono di 12,85 volte in luminanza — quindi una tavolozza generata va poi verificata nel contrasto.

I quattro schemi sono quattro addizioni

Ogni armonia di colore con un nome è un insieme di scarti applicati a un angolo di tinta. Il complementare è base + 180. La triade è base, base + 120, base + 240. L'analogo è base − 30, base, base + 30, anche se alcuni strumenti usano 20 o 45. Il complementare diviso è base + 150 e base + 210, cioè il complementare allargato di 30° per lato. Il tetradico, detto anche quadrato, è base + 90, + 180 e + 270. Il monocromatico non cambia alcuna tinta e varia solo saturazione e chiarezza.

Facciamo un esempio. Prendi un blu medio alla tinta 210. Il complementare dà (210 + 180) mod 360 = 30, un arancione. La triade dà 210, (210 + 120) mod 360 = 330 e (210 + 240) mod 360 = 90 — blu, rosa e verde giallastro. L'analogo dà 180, 210, 240 — ciano, blu, blu violaceo. Il complementare diviso dà (210 + 150) mod 360 = 0 e (210 + 210) mod 360 = 60 — rosso e giallo. Il tetradico dà 210, 300, 30, 120. L'algoritmo non ha altro; il modulo è l'unica finezza, ed esiste solo perché il cerchio si richiude.

Poiché gli scarti toccano solo la tinta, saturazione e chiarezza passano invariate. È esattamente ciò che dà al risultato la sua aria di famiglia — ed esattamente ciò che provoca il fallimento descritto più avanti, perché tenere S e L costanti non tiene costante nulla di ciò che misurano l'occhio umano o un verificatore di accessibilità.

Ci sono due ruote, e non concordano sul blu

Chiedi a un pittore che cosa sta di fronte al blu e la risposta è arancione. Chiedilo a un generatore di schemi e la risposta è giallo. Hanno ragione entrambi, perché ruotano su cerchi diversi. La ruota del pittore ha rosso, giallo e blu come primari — il modello RYB, sistematizzato da Itten e insegnato da allora nelle scuole d'arte — e descrive la mescolanza sottrattiva dei pigmenti. La ruota del generatore è l'asse delle tinte di HSL, una riparametrizzazione cilindrica del cubo RGB additivo, i cui primari sono rosso, verde e blu e i cui secondari sono ciano, magenta e giallo.

I numeri rendono concreta la frattura. Proiettando il cubo RYB su sRGB con la classica interpolazione trilineare, il blu primario RYB cade vicino a #2a5f99 e l'arancione RYB a #ff8000, e occupano angoli opposti di quel modello. Sulla ruota HSL, il blu è la tinta 240 e 240 + 180 = 60, che rende #ffff00, giallo puro. Nessuna ruota mente. RYB descrive che cosa accade mescolando pittura; HSL descrive che cosa accade mescolando luce. Sono modelli di processi fisici diversi e nulla obbliga i loro opposti a coincidere.

Vale la pena saperlo prima di contestare l'output di un generatore. Se un riferimento di design ti dice che il complemento del tuo blu di marca è un arancione caldo e lo strumento ti restituisce un giallo limone, nessuno ha sbagliato — stai semplicemente leggendo due sistemi di coordinate. Se vuoi la risposta pittorica, alcuni generatori offrono una modalità RYB; altrimenti ruota di 30°-40° in meno rispetto al complementare matematico e atterri nella zona arancione.

Una luminosità HSL uguale non è luce uguale

Ecco la dimostrazione. Rendi nove tinte esattamente come hsl(H 100% 50%) — saturazione identica, luminosità nominale identica — e passa ciascuna nel calcolo della luminanza relativa WCAG: dividi ogni canale per 255, linearizzalo e pesa i risultati 0,2126 rosso, 0,7152 verde, 0,0722 blu. La tinta 60 dà #ffff00 con luminanza relativa 0,9278. La tinta 240 dà #0000ff con 0,0722. Lo stesso cursore di luminosità, 12,85 volte la luce.

Le conseguenze si vedono subito in termini di contrasto. Su fondo bianco quel giallo fa 1,07:1 — invisibile — mentre quel blu fa 8,59:1 e supera ogni livello WCAG. L'uno contro l'altro, la coppia fa 8,00:1, ed è per questo che giallo su blu è una combinazione classica della segnaletica e giallo su bianco un errore classico. La scansione completa è istruttiva: la tinta 0 (#ff0000) ha luminanza 0,2126, la tinta 120 (#00ff00) 0,7152 e la tinta 180 (#00ffff) 0,7874. La dispersione su un solo anello di luminosità copre quasi l'intero intervallo da 0 a 1.

Il motivo sta nei pesi. La luminanza relativa è dominata dal verde, che ne porta il 71,52 %, mentre il blu ne porta il 7,22 %. La chiarezza di HSL non ne sa nulla: è definita come il punto medio fra il canale RGB maggiore e il minore, una grandezza puramente geometrica. A L = 50 % e S = 100 % ogni tinta è per costruzione un colore i cui canali coprono l'intero intervallo da 0 a 255, e quanta luce ciò rappresenti dipende interamente da quali canali sono accesi.

Che cosa intendono OKLCH e CIELAB per chiarezza

CIELAB, pubblicato dalla CIE nel 1976, fu il primo tentativo ampiamente adottato di spazio percettivamente uniforme. Il suo asse L* va da 0 a 100 ed è costruito su una compressione a radice cubica della luminanza, scelta perché passi numerici uguali sembrino passi uguali. Passaci lo stesso giallo e lo stesso blu: L* = 97,14 per #ffff00 e 32,30 per #0000ff. Restano molto diversi, ma ora la differenza è onesta — il giallo appare davvero molto più chiaro — e il rapporto è sceso da 12,85 a 3,01.

OKLab, pubblicato da Björn Ottosson nel 2020 e ora parte di CSS Color 4 come oklab() e oklch(), migliora CIELAB proprio dove CIELAB è notoriamente debole: la costanza di tinta per i blu e la prevedibilità al variare della chiarezza. Il suo L va da 0 a 1. Il giallo vale 0,968, il blu 0,452. La sua forma polare, OKLCH, ti dà chiarezza, croma e tinta — gli stessi tre concetti che HSL dichiara di offrire, ma con i primi due che significano ciò che dicono. Conta, perché una tavolozza generata a chiarezza OKLCH fissa sembra davvero una famiglia di colori alla stessa luminosità, che è esattamente ciò che i designer credevano di chiedere a HSL.

Una riserva va detta chiaramente. La chiarezza percettiva non è ancora la luminanza relativa, e né la L di OKLCH né la L* di CIELAB sono ciò che calcola un verificatore WCAG. Due colori con chiarezza OKLCH identica possono avere luminanze relative diverse, quindi pareggiare la chiarezza in modo percettivo non pareggia i rapporti di contrasto. Ottieni una tavolozza che sembra coerente; non ottieni una tavolozza che passa.

Rotazioni uguali non sono passi uguali

Il problema della chiarezza ha un gemello sull'asse delle tinte. Ruotare di 30° nella zona verde — dalla tinta 90 alla 120 — porta fra due verdi che la maggior parte della gente faticherebbe a distinguere. Ruotare di 30° nella zona blu-magenta — da 240 a 270 — porta dal blu puro a un viola nettamente diverso. Gli angoli sono identici; le distanze percepite no, perché la tinta di HSL deriva dagli spigoli del cubo RGB e il cubo non è una sfera percettiva.

Guarda che cosa fa a una triade. Ruotare di 120° dalla tinta 0 dà #ff0000, #00ff00 e #0000ff, con luminanze relative 0,2126, 0,7152 e 0,0722 e chiarezze OKLCH 0,628, 0,866 e 0,452. Un membro è quasi il doppio più chiaro di un altro in termini percettivi e dieci volte in termini fisici. Ruota la stessa triade dalla tinta 90 e ottieni #80ff00, #0080ff e #ff0080, con chiarezze OKLCH 0,891, 0,615 e 0,645 — un equilibrio del tutto diverso, dalla stessa costruzione, cambiando solo l'angolo di partenza.

Ecco perché una triade a volte sembra equilibrata e a volte sembra un colore che urla sopra altri due, senza cambiare metodo. Il metodo ne è cieco. Generare lo stesso schema in OKLCH — ruotare la tinta, tenere L e C — rimuove gran parte della varianza, perché la chiarezza che stai tenendo è ora quella che vedi.

La geometria non sa nulla del contrasto

Prendi un vero blu di marca, #2563eb. In HSL è hsl(221.2 83.2% 53.3%) e su bianco fa 5,17:1 — comodamente oltre la soglia di 4,5:1 per il testo di corpo in WCAG AA. Ora genera la sua famiglia nel modo consueto, ruotando la tinta a saturazione e chiarezza fisse. Il compagno di triade a +120° è #eb2563 e fa 4,22:1: bocciato. Il complementare diviso a +150° è #eb4a25, 3,79:1: bocciato. Il complementare a +180° è #ebad25, 1,99:1: bocciato. A +210° è #c6eb25, 1,37:1: bocciato. A +240°, #63eb25, 1,56:1: bocciato. Cinque rotazioni, cinque bocciature, da una base che passa.

Non è andato storto nulla. Il generatore ha fatto esattamente ciò che dichiara: ha ruotato una tinta. Il contrasto è una proprietà di una coppia di colori, e a un generatore di schemi si consegnano un colore e un angolo. Non ha alcuno sfondo con cui confrontare né modo di sapere quale dei suoi output finirà come testo e quale come riempimento decorativo. Aspettarsi una tavolozza accessibile da una rotazione è aspettarsi la risposta a una domanda mai posta.

Il processo che funziona ha due passi, non uno. Genera la geometria per trovare tinte che stanno insieme, poi regola la chiarezza colore per colore finché ciascuno supera il rapporto che gli serve rispetto alla superficie su cui starà. In pratica significa tenere tinta e croma dati dal generatore e muovere solo la chiarezza — esattamente l'operazione che OKLCH rende prevedibile e HSL no. I ruoli di testo richiedono 4,5:1, il testo grande e i bordi di interfaccia 3:1, e i riempimenti decorativi nulla, quindi la maggior parte delle tavolozze finisce con due varianti di chiarezza per tinta anziché una.

Luminanza relativa WCAG
Nove tinte a luminosità HSL (50 %) e saturazione (100 %) identiche, misurate in tre modi
TintasRGBLuminanza relativa WCAGContrasto su biancoL OKLCHL* CIELAB
60 (giallo)#ffff000,92781,07:10,96897,14
180 (ciano)#00ffff0,78741,25:10,90591,11
120 (verde)#00ff000,71521,37:10,86687,73
30 (arancione)#ff80000,36702,52:10,73267,05
300 (magenta)#ff00ff0,28483,14:10,70260,32
0 (rosso)#ff00000,21264,00:10,62853,24
270 (viola)#8000ff0,11816,25:10,53140,91
240 (blu)#0000ff0,07228,59:10,45232,30
Generatore di schemi di coloreDeriva schemi complementare, analogo, triadico, tetradico, diviso e monocromatico da un colore base.Prova lo strumento

Domande frequenti

Perché la mia tavolozza a triade sembra sbilanciata se la matematica è simmetrica?
Perché la simmetria sta negli angoli, non nell'aspetto. Una triade ruota due volte di 120°, cosa perfettamente regolare sul cerchio delle tinte, ma quel cerchio non è percettivamente regolare. Dalla tinta 0 in hsl(H 100% 50%) si ottengono #ff0000, #00ff00 e #0000ff con luminanze relative 0,2126, 0,7152 e 0,0722 — il verde emette dieci volte la luce del blu a chiarezza HSL identica. In OKLCH le chiarezze sono 0,628, 0,866 e 0,452, ancora una dispersione di quasi due a uno. Il membro che finisce nell'arco verde o giallo dominerà, perché quelle tinte portano quasi tutta la luminanza in sRGB. Il rimedio non è abbandonare la triade ma pareggiare dopo: converti i tre in OKLCH, assegna loro la stessa L e ri-rendi. Le tinte restano dove la geometria le ha messe e il peso visivo si pareggia. Se il tuo strumento non lo sa fare, regolare a occhio la luminosità HSL colore per colore ti porta quasi a destinazione — solo che non puoi farlo tenendo un numero costante.
Il complemento del blu è arancione o giallo?
Entrambi, a seconda della ruota su cui ti trovi. Sulla ruota RYB degli artisti — rosso, giallo e blu come primari, il modello sistematizzato da Itten e ancora insegnato nelle scuole d'arte — il blu sta di fronte all'arancione, perché l'arancione è ciò che si ottiene mescolando gli altri due primari. Sulla ruota RGB che HSL espone, il blu è la tinta 240, il suo opposto è la tinta 60, e la tinta 60 a piena saturazione è #ffff00, giallo puro. Proiettando il cubo RYB su sRGB, il blu RYB finisce vicino a #2a5f99 e l'arancione RYB a #ff8000, e sono davvero angoli opposti di quel modello. Nessuno dei due sbaglia. RYB modella la mescolanza sottrattiva dei pigmenti, dove blu e arancione si annullano verso un marrone neutro; RGB modella la mescolanza additiva della luce, dove luce blu e luce gialla sommano bianco. Il software lavora con la luce, quindi il complementare di un generatore sarà il giallo. Se vuoi il risultato pittorico, usa uno strumento con modalità RYB oppure ruota di 30°-40° in meno rispetto al complementare matematico — dalla tinta 240 atterri intorno alla tinta 30, che rende #ff8000, l'arancione che ti aspettavi.
Devo generare tavolozze in OKLCH invece che in HSL?
Per tutto ciò in cui i membri devono sembrare pari, sì. Tenere costante la chiarezza OKLCH lungo una rotazione dà colori che appaiono davvero ugualmente chiari, mentre tenere costante la luminosità HSL dà la dispersione di luminanza di 12,85 volte fra giallo e blu descritta sopra. OKLCH è supportato in CSS come oklch() e fa parte della specifica CSS Color 4, quindi puoi scriverlo direttamente invece di convertire. Due cautele pratiche. Primo, OKLCH descrive colori fuori dal gamut sRGB, quindi un croma che funziona a una tinta può essere irraggiungibile a un'altra — i browser fanno gamut mapping, ma il risultato avrà meno croma di quello richiesto, e una tavolozza definita con un unico C alto non uscirà uniformemente satura. Tieni C conservativo, fra 0,10 e 0,15, se ti serve che ogni tinta sopravviva. Secondo, la chiarezza OKLCH non è ancora la luminanza relativa WCAG, quindi tavolozze a L uguale non sono tavolozze a contrasto uguale. Devi comunque verificare ogni colore contro ogni sfondo che incontrerà. Ciò che OKLCH ti dà è che la verifica di solito passerà o fallirà per tutti i membri insieme, invece che uno alla volta per ragioni che nessuno sa spiegare.
Una rotazione di tinta maggiore significa sempre più contrasto?
No, e i controesempi sono facili da produrre. Il contrasto secondo WCAG dipende solo dalla luminanza relativa, e la rotazione di tinta cambia la luminanza in un modo che non ha nulla a che vedere con quanto hai ruotato. Da #2563eb la rotazione che danneggia meno il contrasto è +120° con 4,22:1, e quella che lo danneggia di più è +210° con 1,37:1 — ma +180°, la rotazione massima possibile, atterra a 1,99:1, fra le due. La relazione non è monotona perché i pesi di luminanza hanno un picco netto nell'arco verde-giallo: qualsiasi rotazione che ti porti nella regione delle tinte da 60 a 120 alzerà la luminanza e, su fondo bianco, abbasserà il contrasto, che tu ci sia arrivato ruotando di 90° o di 250°. Il modello mentale utile: la rotazione di tinta è una camminata su un rilievo la cui quota è la luminanza, con una montagna verso il verde-giallo e una valle verso il blu. La distanza percorsa non dice nulla sulla quota raggiunta. Se due colori devono contrastare, distanzia deliberatamente le loro chiarezze; ruotare la tinta da sola non è una strategia di contrasto, è una strategia di tinta.
Di quanti colori ha davvero bisogno uno schema?
Meno tinte e più gradini di chiarezza di quanti ne suggerisca la maggior parte dei generatori. Una tavolozza di interfaccia che funziona è tipicamente una tinta di marca, una rampa neutra e al massimo un accento, ciascuno esistente in diverse varianti di chiarezza — una tinta chiara di superficie, un riempimento medio e una versione scura utilizzabile per il testo. Quest'ultima spiega perché il conto cresce: come mostrato sopra, una stessa tinta alla stessa chiarezza non può servire insieme da fondo lavato e da testo leggibile, perché uno di quei ruoli richiede 4,5:1 contro il bianco e l'altro deve esservi appena visibile. Il compito del generatore è trovare le relazioni di tinta; il tuo è dare a ogni tinta i due o tre gradini di chiarezza che i suoi ruoli richiedono. Uno schema a quattro tinte con una variante ciascuna fallirà da qualche parte; uno a due tinte con cinque varianti ciascuna no. Se stai contando, punta a una rampa neutra di nove gradini, cinque gradini della tinta di marca e cinque dell'accento, e verifica quelli destinati al testo contro ogni superficie su cui possono comparire.

Articoli che potrebbero interessarti

Tutte le guide
SpiegazioneGradienti, banding e perché il centro sembra spentoInterpolare in sRGB fa la media di numeri codificati in gamma: il punto medio da rosso a verde è #808000 quando la risposta a mezza luce è #bcbc00 — il 57,2 % di luce in meno. Il banding è un problema aritmetico distinto: 8 bit danno 256 gradini, e un gradiente scuro può averne solo 28. Ecco entrambi, calcolati.SpiegazioneRapporto di contrasto: come lo calcola davvero la WCAGIl 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.TutorialHEX vs RGB: leggere e convertire i coloriHEX e RGB sono due notazioni per gli stessi colori. Ecco come leggere ciascuna, convertire tra loro e aggiungere trasparenza.GuidaChe cosa può togliere un minificatore e che cosa non deve toccareLa minificazione deve preservare il significato, e la parte interessante è lo spazio bianco che porta significato: il combinatore discendente, gli spazi dentro calc(), lo stacco fra due elementi inline. Misurato qui su file reali, compreso ciò che brotli ti avrebbe risparmiato comunque.SpiegazioneCome funzionano davvero i font di Instagram (non sono font)Nulla viene formattato. Ogni lettera è sostituita da un diverso carattere Unicode che sembra grassetto o corsivo, ed è per questo che uno screen reader legge un guazzabuglio e alcuni dispositivi mostrano quadratini vuoti.SpiegazioneChe cosa consente davvero una percentuale di disponibilitàTre nove suonano come una promessa, finché non li dividi in minuti. Che cosa compra il 99,9 % all'anno, al mese, alla settimana e al giorno; perché la finestra di misura conta molto più del nove in più; e i due mesi diversi che questo strumento usa per lo stesso identificatore.

Strumenti correlati

Fonti

Hai notato un errore in questo articolo?