UI Design

Creare color token scalabili per i design system

Dal team di colorPaletteFinder9 min di lettura

Il primo design system che ho costruito è stato pubblicato con esattamente un errore che ha perseguitato tutto ciò che è venuto dopo: usavamo blue-500 direttamente nel codice dei componenti. I pulsanti erano blue-500. I link erano blue-500. Il focus ring era blue-500. Sembrava pulito e DRY. Poi il marketing ha fatto un rebranding dal blu al verde acqua, e abbiamo passato due sprint a rincorrere ogni riferimento esadecimale letterale in quaranta repository — perdendone comunque qualcuno, perché alcuni erano stati scritti a mano come #3B82F6 anziché come la variabile. Tutto quell'episodio avrebbe potuto essere una modifica di una riga. Il motivo per cui non lo è stato è l'intera ragione per cui esistono i color token dei design system.

Un color token è semplicemente un riferimento con un nome a un valore di colore. Ma il valore dei token non sta nella denominazione — sta nei livelli di denominazione, e nell'essere disciplinati su quale livello un componente è autorizzato a toccare. Azzecca la stratificazione e un rebranding, un lancio della dark mode o una correzione di contrasto diventano una modifica piccola e contenuta. Sbagliala e ti ritrovi con la pulizia su quaranta repository.

Tre livelli: primitivo, semantico, componente

Il modello mentale che ha retto in ogni sistema su cui ho lavorato è una gerarchia a tre livelli. Ogni livello fa riferimento a quello sottostante, e ai componenti è sempre e solo consentito consumare il livello superiore.

Ecco la regola che fa funzionare l'intera cosa, e quella che la maggior parte dei team salta: i componenti fanno riferimento a token semantici o di componente, mai a primitivi. Nel momento in cui un pulsante scavalca il livello semantico per afferrare direttamente blue-500, hai reintrodotto il problema dei quaranta repository un componente alla volta.

Perché preoccuparsi del livello semantico intermedio? Perché è la cucitura in cui avviene il theming. Quando passi alla dark mode, non ridipingi ogni componente — ripunti qualche decina di token semantici a primitivi diversi. I componenti non sanno che è cambiato qualcosa. Quell'indirezione è la singola decisione a più alta leva dell'intero sistema.

Costruisci la scala prima di nominare qualsiasi cosa

Prima delle semantiche, ti servono primitivi a cui valga la pena puntare — e questo significa una scala numerica, convenzionalmente da 50 a 950. La convenzione resa popolare da Tailwind (50, 100, 200 … 900, 950) è di fatto diventata il default di settore, e vale la pena adottarla solo perché i nuovi sviluppatori si sentano a casa. 50 è la tinta più chiara, 500 è all'incirca la tinta di marca pura, 950 è quasi nero.

L'errore che fanno i principianti è generare una scala schiarendo e scurendo ingenuamente in sRGB — aggiungendo bianco per le tinte chiare, nero per quelle scure. Ottieni mezzitoni fangosi e gradini che non sembrano uniformemente distanziati, perché la luminosità percepita non è lineare in RGB. Un 400 finisce per apparire quasi identico a un 500, mentre da 700 a 800 c'è un dirupo. La soluzione è distanziare i gradini in un modello percettivo. Gli strumenti che lavorano in HSL ti portano gran parte della strada; quelli che usano OKLCH (ora ben supportato in CSS) ti portano il resto, perché OKLCH mantiene la luminosità percepita coerente al variare della tinta. Se vuoi valutare a occhio tinte e sfumature armoniose in fretta mentre prototipi una scala, il generatore di palette di colori è un modo veloce per vedere le relazioni tra le tinte prima di fissarle nei token.

Qualche regola guadagnata sul campo per le scale:

Denominazione: descrivi il lavoro, non l'aspetto

Il singolo principio di denominazione più importante è questo: i token semantici e di componente dovrebbero nominare lo scopo, mai l'aspetto. Un token chiamato color-text-red si rompe il giorno in cui il testo di errore diventa arancione — il nome ora mente. Un token chiamato color-feedback-error sopravvive a quel cambiamento intatto, perché "error" resta vero indipendentemente da quale rosso (o arancione) si risolva.

Una struttura di denominazione che scala si legge dalla categoria generale al modificatore specifico, perché si ordina e si controlla in modo pulito:

Quest'ultimo — color-text-on-action — è un token che la gente dimentica e di cui poi si pente. Quando il tuo colore d'azione è un blu saturo, il testo su quel blu deve essere bianco o quasi bianco, e quell'abbinamento non ha nulla a che fare con color-text-primary. Rendilo un token esplicito o vedrai gli sviluppatori scrivere a mano #FFFFFF sui pulsanti per poi aprire bug di accessibilità quando qualcuno introduce un'azione "warning" giallo chiaro.

Per i primitivi, nominare l'aspetto è corretto e atteso — blue-500 dovrebbe descrivere cos'è, perché è proprio quello il senso del livello primitivo. La regola "descrivi l'intento" si applica al di sopra di esso.

Implementazione con le CSS custom properties

Le CSS custom properties sono la casa naturale dei token perché si propagano nella cascata e possono essere sovrascritte dal contesto — che è esattamente come funziona il theming. Lo schema prevede due livelli di variabili: i primitivi definiti una volta sola alla radice, le semantiche definite alla radice e ridefinite sotto un selettore di tema.

Definisci i primitivi globalmente — --blue-600: #2563EB, --gray-900: #111827, e così via. Poi mappa le semantiche su di essi: --color-action-primary: var(--blue-600) e --color-text-primary: var(--gray-900). I componenti leggono sempre e solo le variabili semantiche: color: var(--color-text-primary). Non nominano mai un primitivo e non nominano mai un codice esadecimale.

In Tailwind v4 questo si mappa in modo pulito sulla nuova direttiva @theme, che sia dichiara i token sia genera utility da essi, esponendo ogni token come variabile CSS a runtime. Definisci --color-text-primary: var(--color-gray-900) dentro @theme, usi text-primary nel markup e sovrascrivi la variabile sotto un selettore .dark nel tuo base layer. La documentazione ufficiale sulle theme variables di Tailwind copre i meccanismi, e la stessa disciplina a due livelli si applica indipendentemente dal framework.

La dark mode è un problema di rimappatura dei token, non un problema di colore

Il motivo per cui il livello semantico si guadagna il suo posto è che la dark mode diventa quasi banale una volta che esiste. Non tocchi un singolo componente. Sovrascrivi i token semantici all'interno di uno scope .dark (o [data-theme="dark"]) così che si risolvano in primitivi diversi.

In light mode, --color-bg-surface: var(--white) e --color-text-primary: var(--gray-900). In dark mode, sotto il selettore .dark, --color-bg-surface: var(--gray-900) e --color-text-primary: var(--gray-100). Ogni componente che legge color-bg-surface si capovolge automaticamente, perché è la cascata a fare il lavoro.

Due cose che fregano la gente qui:

Le insidie che si rompono davvero su larga scala

Dopo abbastanza rebranding e lanci di temi, le modalità di fallimento fanno rima:

Il riassunto onesto è che i token non prendono le decisioni di colore al posto tuo — rendono le decisioni di colore economiche da cambiare. La struttura è ciò che ti compra la possibilità di fare un rebranding di venerdì, pubblicare la dark mode in uno sprint e correggere un bug di contrasto in una riga invece che in quaranta repository. Spendi i tuoi sforzi sul livello semantico e sulla denominazione, mantieni i componenti onesti su quale livello toccano, e il sistema assorbirà cambiamenti che non puoi nemmeno ancora prevedere.

Domande frequenti

Qual è la differenza tra color token primitivi e semantici?

Un token primitivo (o di base) dà un nome a un valore di colore grezzo senza alcun contesto — per esempio blue-500: #3B82F6. Descrive cos'è il colore. Un token semantico dà un nome allo scopo del colore, come color-action-primary o color-feedback-error, e punta a un primitivo. I componenti dovrebbero fare riferimento ai token semantici, mai ai primitivi, così che un rebranding o il theming richieda solo di ripuntare il livello semantico anziché modificare ogni componente.

Come dovrei nominare i color token in un design system?

Nomina i token semantici e di componente per intento, non per aspetto. color-feedback-error sopravvive a un redesign in cui il rosso di errore diventa arancione; color-text-red no. Usa una struttura dal generale allo specifico come category-property-variant-state (es. color-bg-surface-raised, color-action-primary-hover). I token primitivi sono l'eccezione — dovrebbero descrivere il colore letterale, come gray-900, perché quello è il loro intero scopo.

Perché usare una scala numerica da 50 a 950?

La scala da 50 a 950 (50, 100, 200 … 900, 950) resa popolare da Tailwind è diventata un default di settore, quindi è immediatamente familiare ai nuovi sviluppatori. 50 è la tinta più chiara, ~500 è la tinta di marca pura e 950 è quasi nero. Distanziare i gradini in un modello percettivo come HSL o OKLCH — anziché mescolare ingenuamente bianco e nero in sRGB — fa sì che i gradini risultino uniformemente distanziati ed evita mezzitoni fangosi.

Come rendono i color token più facile la dark mode?

La dark mode diventa un problema di rimappatura dei token anziché un redesign. Poiché i componenti leggono solo i token semantici, sovrascrivi quei token all'interno di uno scope .dark o [data-theme=dark] così che si risolvano in primitivi diversi — per esempio color-bg-surface punta al bianco in light mode e a gray-900 in dark. La cascata CSS capovolge automaticamente ogni componente, senza modifiche al codice dei componenti. Ricorda di desaturare i colori d'accento ed evitare sfondi neri puri per ridurre l'affaticamento visivo e l'alonatura.

Quali rapporti di contrasto devono soddisfare i color token per l'accessibilità?

Le WCAG richiedono un rapporto di contrasto di almeno 4.5:1 per il testo normale e 3:1 per il testo grande e i componenti di interfaccia. Integra queste verifiche nel tuo sistema di token validando ogni abbinamento semantico di testo-su-sfondo in ciascun tema. Una combinazione che passa in light mode può fallire in dark mode, quindi testa entrambi, e definisci un token esplicito testo-su-azione per garantire che le etichette sui pulsanti saturi restino leggibili.

Vuoi sperimentare con i colori?

Prova il nostro generatore di palette gratuito per trovare la tua armonia perfetta — con verifica del contrasto WCAG integrata.

Apri il generatore