Skip to content
Torna al blog
Tutorial

Spazio colore OKLCH spiegato — perché Tailwind v4 l'ha adottato

Perché OKLCH è diventato lo standard dei design system nel 2024-2026. Differenze da HSL e LCH, con una conversione HEX→OKLCH passo passo, online e gratis.

14 min di lettura

Spazio colore OKLCH spiegato — perché Tailwind v4 l’ha adottato

Apra il sorgente di un design system qualsiasi del 2025 — shadcn/ui, Radix Themes, la palette di base di Tailwind v4 — e la prima cosa che salta all’occhio sono i colori. Non codici hex, non triplette hsl(), ma una funzione di cui tre anni fa nessuno parlava: oklch(). Tailwind v4 spedisce l’intera palette di default come letterali OKLCH. shadcn ora genera temi che emettono custom property OKLCH. Il design system di Vercel è stato ricostruito intorno a OKLCH nel 2024.

Non è un fenomeno di moda. C’è una ragione precisa, matematica, per cui ogni design system serio ha cambiato modello colore in silenzio, e una volta che la si vede non si può più tornare indietro a non vedere perché HSL è sempre stato lo strumento sbagliato per ciò che lo si usava a fare.

Questo articolo percorre quella ragione partendo dai principi, si chiude con una conversione passo passo da un codice hex a OKLCH e Le fornisce una ricetta di migrazione per la Sua palette.

Quando gli spazi colore si sono rotti

I design system hanno un lavoro tonale. Un pulsante in hover è leggermente più chiaro del suo stato a riposo. Una scheda muted sta un gradino più scura della superficie circostante. Un focus ring deve essere visibilmente più luminoso del chrome neutro che gli sta dietro. Farlo bene, su scala, richiede che “più chiaro” e “più scuro” significhino la stessa cosa attraverso ogni tonalità della palette.

Era un requisito facile da ignorare quando le palette avevano otto colori e tre stati. È diventato scomodo quando i team hanno cominciato a spedire scale a 11 stop (50-950 nella convenzione di Tailwind), otto colori semantici, varianti chiare e scure e accenti brand che dovevano convivere con i colori di sistema di iOS, Android e Web. All’improvviso la domanda “questo teal-500 ha la stessa luminosità del nostro blue-500?” è diventata un vero problema d’ingegneria, non un lusso di art direction.

HSL — il modello di lavoro da CSS 3 — non sapeva rispondere. Due colori HSL con valori L identici possono apparire drammaticamente diversi nella luminosità percepita. Un giallo HSL puro a lightness: 50% appare di gran lunga più luminoso di un blu HSL puro alla stessa luminosità. L’occhio non percepisce giallo e blu nella stessa misura; HSL è stato progettato per essere intuitivo nei picker, non percettivamente coerente per le scale. Entro il 2023 ogni design system che cresceva oltre una manciata di colori stava rappezzando il problema con script di miscelazione custom o override regolati a mano.

Ciò che serviva era un modello colore in cui L significasse davvero “la luminosità percepita che un essere umano riporterebbe” e in cui ruotare la tonalità o ridurre la saturazione non cambiasse invisibilmente la luminosità come effetto collaterale. Quel modello esisteva nella color science accademica — semplicemente non era ancora arrivato a CSS.

Il problema di HSL, in concreto

Li metta in un browser e li guardi affiancati:

.a { background: hsl(60 100% 50%); }   /* yellow */
.b { background: hsl(240 100% 50%); }  /* blue */
.c { background: hsl(120 100% 50%); }  /* green */

Tutti e tre hanno L: 50%. Nessuno dei tre sembra avere la stessa luminosità. Il giallo quasi brucia; il blu si legge quasi nero su una pagina bianca; il verde si trova in mezzo. Se costruisce uno stato hover sommando 10% a L, l’hover del giallo è appena visibile mentre l’hover del blu è uno spostamento drammatico. La rifinitura dell’interazione finisce per dipendere dalla tonalità da cui il designer è partito per caso.

Non è un bug di HSL. HSL è stato progettato nel 1978 per color picker da paint-by-numbers, in cui gli utenti manipolavano tonalità, saturazione e “lightness” — definita come (max(R,G,B) + min(R,G,B)) / 2 — per comporre un colore. La matematica non ha alcuna nozione della percezione umana. La lightness di HSL è un punto medio geometrico dei canali sRGB, nient’altro.

La CIE — l’organismo internazionale di standardizzazione per la colorimetria — conosceva il problema dagli anni ‘70. Hanno pubblicato due spazi percettivamente uniformi, CIELAB e CIELUV, che definivano la lightness come qualcosa di più vicino a quello che la visione umana fa davvero. Entro gli anni ‘90 CIE LAB era lo standard nella stampa, fotografia e gestione del colore. Ma la sua conversione in RGB è contorta, e CSS non l’ha mai adottato in maniera diffusa. Gli sviluppatori web hanno continuato a usare HSL non perché fosse giusto ma perché c’era.

CIE LAB / LCH: la soluzione accademica, con i suoi problemi

CIELAB prende un valore tristimolo XYZ (un modello di come i coni umani rispondono alla luce) e lo fa passare attraverso una radice cubica e una rotazione 2D per produrre tre canali: L* (lightness, 0-100), a* (verde ↔ rosso) e b* (blu ↔ giallo). LCH è lo stesso spazio espresso in forma polare: L*, C* (chroma, distanza dal neutro), H* (angolo di tonalità).

Questi spazi sono percettivamente uniformi in un senso misurabile. Un ΔE di 1 — un passo unitario in qualsiasi direzione nello spazio LAB — è approssimativamente la più piccola differenza di colore che un osservatore allenato sappia rilevare. I flussi di lavoro di stampa e prestampa girano su LAB e LCH da decenni.

Allora perché CSS non ha semplicemente adottato LCH ed è andato avanti?

Due ragioni. Primo, CIE LAB era calibrato contro una specifica condizione di visione (un osservatore standard a 2° sotto illuminazione D50) ottimizzata per la riflettanza di superficie, non per i display emissivi. Sugli schermi la sua uniformità percettiva deriva — colori che sono “ugualmente luminosi” in LAB non sempre appaiono ugualmente luminosi su un telefono. Secondo, la gamma di LCH è scomoda. Esistono colori visibili che LAB descrive bene ma che si trovano fuori dalle gamme cromatiche comuni dei display, e la mappatura da LCH a sRGB occasionalmente produce spostamenti di tonalità (il blu vira leggermente al porpora quando si riduce il chroma). Per il lavoro sui design system, entrambi sono deal-breaker.

CSS Color 4 ha aggiunto lab() e lch() nel 2021, e funzionano nei browser moderni. Ma per il problema specifico di costruire scale tonali coerenti su schermi emissivi, la community ha continuato a cercare.

OKLAB / OKLCH: l’intuizione di Ottosson del 2020

A dicembre 2020 Björn Ottosson — un color engineer svedese — ha pubblicato un paper intitolato “A perceptual color space for image processing”. Il paper era piccolo: tre matrici brevi, uno step di radice cubica, nessuna tabella di calibrazione, nessun dato di riferimento coperto da copyright. Ottosson ha preso gli esistenti modelli colore IPT e CAM16-UCS — spazi accademici con buone proprietà ma matematica brutta — e ne ha derivato uno spazio più semplice che approssimava il loro comportamento percettivo usando moltiplicazioni di matrici ordinarie su valori tristimolo XYZ a luce lineare.

L’ha chiamato OKLAB. La forma polare è OKLCH.

Ciò che rende OKLCH speciale non è la novità — è la sua adeguatezza allo scopo. Tre proprietà insieme:

  1. La lightness in OKLCH è davvero percettiva. Un giallo puro a L: 0.7 e un blu puro a L: 0.7 sembrano avere la stessa luminosità su un display calibrato. Gli stati hover definiti come L + 0.05 producono spostamenti visivamente equivalenti su tutta la palette.
  2. La tonalità è preservata sotto cambi di chroma. Se riduce il valore C di oklch(0.7 0.2 30) a oklch(0.7 0.1 30), la tonalità resta ferma. In LCH la stessa operazione spesso introduce uno spostamento di tonalità visibile. In OKLCH può appiattire il chroma per costruire una variante muted di un colore brand senza che questo derivi accidentalmente verso una tonalità diversa.
  3. La matematica è economica. Due moltiplicazioni di matrici e una radice cubica. Implementabile in una funzione JavaScript da 30 righe. Nessuna lookup table, nessuna calibrazione per dispositivo, nessuna preoccupazione di licensing.

La combinazione è ciò che ha reso OKLCH utilizzabile in CSS reale. Il W3C ha aggiunto oklch() a CSS Color 4 nel 2022. Chrome 111 l’ha rilasciato nel 2023. Ogni evergreen browser lo supportava entro metà 2024. Tailwind v4 ne ha fatto il formato palette di default lo stesso anno.

La matematica: una conversione HEX → OKLCH passo passo

Percorriamo #3b82f6 — il blue-500 di Tailwind — fino a OKLCH. È la stessa matematica che il Convertitore di colori e lo spoke HEX in OKLCH eseguono a ogni battuta. Sapere cosa sta succedendo sotto il cofano renderà più sensate le insidie della sezione successiva.

Step 1: hex in sRGB. Divida l’hex a sei cifre in tre coppie e divida ciascuna per 255.

const r = 0x3b / 255; // 0.231
const g = 0x82 / 255; // 0.510
const b = 0xf6 / 255; // 0.965

Questi sono valori sRGB gamma-encoded: i valori dei canali come il file immagine li memorizza, con una curva non lineare incorporata per compensare il modo in cui i monitor emettono luce.

Step 2: sRGB a sRGB lineare. Rimuova la curva gamma per ottenere valori di canale a luce lineare. La trasformazione standard piecewise da CSS Color 4 §11.2:

const linear = (v) => v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
const [lr, lg, lb] = [r, g, b].map(linear);
// lr ≈ 0.045, lg ≈ 0.220, lb ≈ 0.923

Step 3: sRGB lineare a XYZ D65. Una moltiplicazione di matrice standard, definita in CSS Color 4 §15.1:

const x = 0.4124564 * lr + 0.3575761 * lg + 0.1804375 * lb;
const y = 0.2126729 * lr + 0.7151522 * lg + 0.0721750 * lb;
const z = 0.0193339 * lr + 0.1191920 * lg + 0.9503041 * lb;
// x ≈ 0.265, y ≈ 0.231, z ≈ 0.927

Questa è la rappresentazione tristimolo XYZ canonica — la forma “quali lunghezze d’onda è questo colore, in termini di risposta dei coni umani”.

Step 4: XYZ in LMS. La prima matrice di Ottosson mappa XYZ in uno spazio di fondamentali dei coni long/medium/short tarato per OKLAB:

const lms = [
  0.8189330101 * x + 0.3618667424 * y - 0.1288597137 * z,
  0.0329845436 * x + 0.9293118715 * y + 0.0361456387 * z,
  0.0482003018 * x + 0.2643662691 * y + 0.6338517070 * z,
];

Step 5: radice cubica dei valori LMS. È lo step di compressione percettiva — analogo alla radice cubica in CIE LAB:

const lms_ = lms.map(Math.cbrt);

Step 6: LMS’ a OKLAB. La seconda matrice di Ottosson:

const L = 0.2104542553 * lms_[0] + 0.7936177850 * lms_[1] - 0.0040720468 * lms_[2];
const a = 1.9779984951 * lms_[0] - 2.4285922050 * lms_[1] + 0.4505937099 * lms_[2];
const b_ = 0.0259040371 * lms_[0] + 0.7827717662 * lms_[1] - 0.8086757660 * lms_[2];
// L ≈ 0.629, a ≈ -0.022, b_ ≈ -0.191

Questo è OKLAB. Il canale di luminosità L ≈ 0.629 è ciò che l’occhio percepisce come “60% di luminosità” per questo particolare blu.

Step 7: OKLAB a OKLCH (cartesiano a polare).

const C = Math.sqrt(a * a + b_ * b_);           // 0.193
const H = (Math.atan2(b_, a) * 180 / Math.PI + 360) % 360; // 263.4
// → oklch(0.629 0.193 263.4)

Quindi #3b82f6 è oklch(0.629 0.193 263.4). Luminosità 0,629, chroma 0,193, tonalità 263,4°.

Se costruisce una scala 50-950 da questo colore variando solo L (da 0,95 fino a 0,15 in 11 step), tenendo C e H fissi, ottiene una palette in cui ogni tonalità appare visibilmente come la stessa tonalità, sfumando in luminosità in modo uniforme. Faccia la stessa cosa in HSL e le tinte scure virano al porpora mentre le tinte chiare diventano grigie. Quella è la vittoria.

Display P3 + Rec2020: perché OKLCH sblocca la gamma estesa

OKLCH è illimitato. A differenza di HSL (limitato a sRGB) e persino di LCH (calibrato per superfici riflettenti), OKLCH non ha alcuna gamma cromatica implicita. Può scrivere oklch(0.7 0.25 30) e produrre un rosso vivido che sta dentro il volume colore di Display P3 ma fuori da quello di sRGB. Su un iPhone o un MacBook recenti, viene renderizzato. Su un monitor più vecchio, il browser lo snappa automaticamente alla rappresentazione sRGB più vicina.

Conta perché Apple, Samsung e il W3C hanno passato la fine degli anni 2010 a spedire hardware wide-gamut. Il MacBook Pro 14” / 16” con mini-LED spedisce P3 di default. L’iPhone 15 Pro renderizza Display P3 in Safari. I flagship Android spediscono pannelli Rec2020. Entro il 2025 una frazione sostanziale del traffico sui design system è su hardware wide-gamut che può mostrare colori che HSL/sRGB semplicemente non possono esprimere.

OKLCH Le permette di scrivere quei colori senza bullonare una dichiarazione @media (color-gamut: p3) separata. Il browser gestisce il fallback. Il Suo design system ottiene un “usa il rosso più luminoso che il dispositivo può renderizzare” out of the box.

È anche per questo che OKLCH è il formato giusto per i design token. Una variabile --brand in OKLCH è una descrizione device-independent dell’intento. Il browser decide cosa renderizzare su qualunque display abbia l’utente, e il Suo codice è portabile attraverso CSS, SwiftUI (che supporta nativamente Display P3 Color), Android Compose (Rec2020-aware) e Flutter.

Tailwind v4 e la rivoluzione dei design token

Tailwind v4 — rilasciato nel 2024 — è stato il punto di inflessione che ha trasformato OKLCH da ricerca a default dell’industria. Gli autori di Tailwind hanno fatto tre scelte opinionated:

  1. La palette di default è OKLCH. Slate, gray, zinc, neutral, stone — ogni colore Tailwind è definito in oklch() nel sorgente. Le scale 50-950 sono uniformi in luminosità percettiva per costruzione.
  2. I temi custom usano blocchi @theme con letterali OKLCH. I colori brand vengono definiti come token oklch(); le utility downstream (bg-brand-500, text-brand-300) sono generate.
  3. Nessuna cerimonia di fallback richiesta. I browser senza supporto OKLCH sono sotto il baseline documentato di Tailwind v4.

Quell’ultima decisione è quella che ha reso l’adozione una scelta senza footgun. Ancora nel 2023, i designer dovevano spedire sia le versioni oklch() sia quelle hsl() di ogni colore perché le vecchie versioni di Safari non si rompessero. Con Tailwind v4, il baseline è browser del 2023 o più recenti, e OKLCH funziona ovunque.

Il generatore di temi di shadcn/ui segue lo stesso pattern: inserisce il Suo colore brand, ottiene una scala OKLCH in output. Il design system di Vercel usa OKLCH per i suoi colori semantici. Le color scale di Radix Themes sono definite in OKLCH. La community è convergente.

Migrazione pratica: palette HEX → palette OKLCH

Se oggi ha una palette basata su hex, la migrazione è meccanica. Ecco la ricetta:

1. Decida la struttura della Sua scala. Il 50-950 di Tailwind (11 stop) è il default de facto e vale la pena seguirlo a meno che non abbia una ragione specifica per fare diversamente. Stop a L = 0,97, 0,93, 0,86, 0,76, 0,63, 0,50, 0,42, 0,34, 0,26, 0,18, 0,10 danno una scala percettiva fluida.

2. Converta il Suo hex brand in OKLCH. Usi il Convertitore di colori o lo strumento HEX in OKLCH. Otterrà una tripletta come oklch(0.629 0.193 263.4). Annoti il valore H — è la tonalità del Suo brand.

3. Tenga C e H costanti; faccia variare L. Costruisca la scala emettendo:

--brand-50:  oklch(0.97 0.193 263.4);
--brand-100: oklch(0.93 0.193 263.4);
...
--brand-950: oklch(0.10 0.193 263.4);

4. Sintonizzi gli stop estremi. A L molto basso (≤ 0,20) e L molto alto (≥ 0,95), valori di chroma elevati cadono fuori sRGB. Riduca C per quegli stop, oppure accetti lo snap automatico del browser. I default di Tailwind riducono il chroma verso entrambe le estremità — copi quel pattern.

5. Definisca alias semantici. --surface: var(--brand-50), --surface-elevated: var(--brand-100), --text-primary: var(--brand-900), ecc. Ora i Suoi design token si leggono come intento, non come colori.

6. Verifichi il contrasto. Usi APCA Lc o i rapporti di contrasto WCAG 2 per confermare che ogni coppia --text-* contro ogni coppia --surface-* superi la Sua soglia di accessibilità. Poiché la L di OKLCH è percettiva, la matematica del contrasto è più affidabile di quanto sarebbe in HSL.

Un team che esegue questa migrazione su una palette legacy a 60 colori tipicamente atterra su una palette OKLCH più piccola e più uniforme di 30-40 token in un solo pomeriggio. La nuova palette spedisce più piccola, genera meno CSS e produce un’animazione tonale visibilmente migliore negli stati hover e disabled senza alcuna messa a punto extra.

Insidie e come gestirle

Alcune cose da sapere all’inizio:

Avvisi fuori gamma. Alcuni valori OKLCH cadono fuori da Display P3 o sRGB. I browser moderni gestiscono lo snap al colore valido più vicino in modo automatico, ma lo snap è lossy: il Suo oklch(0.7 0.25 30) può renderizzarsi leggermente meno saturo di quanto l’abbia scritto. Strumenti come la riga della gamma cromatica del Convertitore di colori Le dicono se il Suo colore è sRGB-safe, P3-safe o Rec2020-safe, e offrono uno snap-to-sRGB a un clic così che ciò che scrive sia ciò che vede.

Stranezze del chroma sub-pixel. Il chroma di OKLCH è illimitato, ma l’intervallo utile è all’incirca da 0 a ~0,4 per i colori visibili. Valori sopra 0,4 sono raggiungibili solo con luce laser monocromatica — non sono colori fisici che un display possa renderizzare. Il Convertitore di colori limita il cursore del chroma a 0,4 per questa ragione; valori oltre non producono alcuna differenza percettibile su nessun display reale.

Supporto browser, stile 2025. Chrome 111+, Safari 15.4+, Firefox 113+ supportano tutti oklch() nativamente. I browser pre-2023 no. Se deve supportare IE/Edge legacy o vecchi Safari mobile (1-3% del traffico a seconda del Suo pubblico), può abbinare una dichiarazione OKLCH a un fallback hex usando @supports (color: oklch(0 0 0)) — ma per i design token che spediscono nel 2025, il costo del fallback spesso supera il beneficio legacy.

Permanenza del codice hex. OKLCH è per l’intento del design system. Il Suo CMS può ancora aver bisogno di un valore hex per ragioni legacy (firme email, documenti Office, checklist di asset brand). Mantenga una lookup table generata che emetta l’hex snapped-to-sRGB per ogni token OKLCH, ma non scriva il codice in hex.

Non confonda OKLCH e OKLAB. OKLAB è la forma rettangolare (canali L, a, b); OKLCH è lo stesso spazio colore in forma polare (L, C, H). Si converte tra i due con uno step cartesiano↔polare. Usi OKLCH per i token (più leggibile, più facile da scalare); usi OKLAB internamente se deve interpolare o miscelare colori.

La provi sulla Sua palette

Il modo più rapido per vedere in azione ciò che abbiamo descritto qui è inserire un hex brand nel Convertitore di colori. Digiti il Suo colore brand nel campo HEX e legga l’output OKLCH. Poi muova i cursori sul lato OKLCH e osservi come la stessa tonalità resta la stessa tonalità mentre riduce il chroma, e come la stessa luminosità resta la stessa luminosità mentre ruota la tonalità. Dopo pochi minuti avrà un senso intuitivo del perché HSL è sempre stato lo strumento sbagliato per le scale tonali, e del perché ogni design system serio è andato avanti.

Per la conversione HEX-a-OKLCH specifica, usi HEX in OKLCH — la stessa matematica di questo articolo, con un beneficio in più: Le mostra la classificazione di gamma cromatica (sRGB / Display P3 / Rec2020) così sa quali dei Suoi colori brand sono sicuri ovunque e quali invece hanno bisogno di un dispositivo wide-gamut per renderizzarsi pienamente.

Questo è OKLCH. Vale la migrazione. Fatta bene, non scriverà mai più hsl().

Articoli correlati

Vedi tutti gli articoli