Le premier design system que j'ai construit a été livré avec exactement une erreur qui a hanté tout le reste : nous utilisions blue-500 directement dans le code des composants. Les boutons étaient en blue-500. Les liens étaient en blue-500. L'anneau de focus était en blue-500. Cela paraissait propre et DRY. Puis le marketing a changé l'identité de marque du bleu vers le turquoise, et nous avons passé deux sprints à traquer chaque référence hexadécimale littérale à travers quarante dépôts — et nous en avons quand même raté quelques-unes, parce que certaines avaient été codées en dur en #3B82F6 plutôt qu'en variable. Tout cet épisode aurait pu n'être qu'un changement d'une ligne. La raison pour laquelle ça ne l'a pas été, c'est toute la raison d'être des tokens de couleur de design system.
Un token de couleur n'est qu'une référence nommée vers une valeur de couleur. Mais la valeur des tokens ne réside pas dans le nommage — elle réside dans les couches de nommage, et dans la discipline qui consiste à décider quelle couche un composant a le droit de toucher. Réussissez la stratification et un changement de marque, un lancement de mode sombre ou une correction de contraste devient une modification petite et contenue. Ratez-la et vous obtenez le nettoyage à quarante dépôts.
Trois couches : primitive, sémantique, composant
Le modèle mental qui a tenu bon dans chaque système sur lequel j'ai travaillé est une hiérarchie à trois niveaux. Chaque niveau référence celui du dessous, et les composants n'ont jamais le droit de consommer que le niveau supérieur.
- Les tokens primitifs (aussi appelés tokens de base ou globaux) sont votre palette brute.
blue-500: #3B82F6,gray-900: #111827,red-600: #DC2626. Ils décrivent ce qu'est la couleur et rien sur l'endroit où elle est utilisée. Un primitif ne doit jamais apparaître directement dans le CSS d'un bouton. - Les tokens sémantiques décrivent l'intention — la mission que remplit la couleur.
color-text-primary,color-bg-surface,color-border-default,color-action-primary,color-feedback-error. Un token sémantique pointe vers un primitif :color-action-primary → blue-600. - Les tokens de composant sont optionnels et décrivent une partie précise d'un composant précis.
button-primary-bg,card-border-color,input-focus-ring. Ils pointent vers des tokens sémantiques, pas des primitifs.
Voici la règle qui fait tenir tout l'édifice, et celle que la plupart des équipes sautent : les composants référencent des tokens sémantiques ou de composant, jamais des primitifs. À l'instant où un bouton passe par-dessus la couche sémantique pour aller chercher blue-500 directement, vous avez réintroduit le problème des quarante dépôts, un composant à la fois.
Pourquoi s'embêter avec la couche sémantique intermédiaire ? Parce que c'est la couture où se produit la thématisation. Quand vous basculez en mode sombre, vous ne repeignez pas chaque composant — vous repointez quelques dizaines de tokens sémantiques vers d'autres primitifs. Les composants ne savent pas que quoi que ce soit a changé. Cette indirection est la décision à plus fort levier de tout le système.
Construisez l'échelle avant de nommer quoi que ce soit
Avant la sémantique, il vous faut des primitifs qui valent la peine d'être pointés — et cela signifie une échelle numérique, conventionnellement de 50 à 950. La convention popularisée par Tailwind (50, 100, 200 … 900, 950) est devenue de fait le standard de l'industrie, et il vaut la peine de l'adopter ne serait-ce que pour que les nouveaux développeurs se sentent chez eux. 50 est la teinte la plus claire, 500 correspond à peu près à la teinte de marque pure, 950 est quasi noir.
L'erreur que font les débutants est de générer une échelle en éclaircissant et en assombrissant naïvement en sRGB — en ajoutant du blanc pour les teintes, du noir pour les ombres. Vous obtenez des demi-tons boueux et des paliers qui ne paraissent pas d'écart régulier, parce que la luminosité perçue n'est pas linéaire en RGB. Un 400 finit par sembler presque identique à un 500, tandis que le passage de 700 à 800 est une falaise. La parade est d'espacer vos paliers dans un modèle perceptuel. Les outils qui travaillent en HSL vous mènent à mi-chemin ; ceux qui utilisent OKLCH (désormais bien pris en charge en CSS) font le reste, parce qu'OKLCH garde la luminosité perçue constante quand la teinte change. Si vous voulez juger rapidement à l'œil des teintes et des ombres harmonieuses pendant que vous prototypez une échelle, le générateur de palette de couleurs est un moyen rapide de voir les relations entre les teintes avant de les figer en tokens.
Quelques règles durement acquises pour les échelles :
- Ancrez votre échelle au contraste, pas à l'esthétique. Décidez tôt quel palier est votre couleur de « texte sur blanc » et vérifiez qu'il atteint les rapports dont vous avez besoin. Dans la plupart des rampes neutres, c'est au
600ou au700que vous franchissez le 4.5:1 sur blanc. Le savoir vous permet d'écrire des règles sémantiques comme « le texte courant utilisegray-700» en toute confiance. - Gardez le même nombre de paliers pour chaque teinte. Si le bleu a un
950et que le vert s'arrête à900, vos mappages sémantiques deviennent irréguliers et le mode sombre tourne au cauchemar de cas particuliers. - Ne surgénérez pas. Onze paliers par teinte suffisent amplement. J'ai vu des équipes livrer des échelles de vingt paliers où la moitié sont visuellement indiscernables et où personne n'utilise jamais le
425.
Nommage : décrivez la mission, pas l'apparence
Le principe de nommage le plus important est celui-ci : les tokens sémantiques et de composant doivent nommer la finalité, jamais l'apparence. Un token nommé color-text-red casse le jour où le texte d'erreur devient orange — le nom ment désormais. Un token nommé color-feedback-error survit intact à ce changement, parce que « erreur » reste vrai quel que soit le rouge (ou l'orange) vers lequel il se résout.
Une structure de nommage qui passe à l'échelle se lit de la catégorie large au modificateur précis, parce qu'elle se trie et s'audite proprement :
catégorie-propriété-variante-étatcolor-bg-surface,color-bg-surface-raised,color-bg-surface-sunkencolor-action-primary,color-action-primary-hover,color-action-dangercolor-text-primary,color-text-secondary,color-text-disabled,color-text-on-action
Ce dernier — color-text-on-action — est un token que les gens oublient et regrettent. Quand votre couleur d'action est un bleu saturé, le texte sur ce bleu doit être blanc ou quasi blanc, et cette paire n'a rien à voir avec color-text-primary. Faites-en un token explicite, sans quoi vous verrez des développeurs coder en dur #FFFFFF sur les boutons puis déposer des bugs d'accessibilité quand quelqu'un introduira une action « avertissement » jaune clair.
Pour les primitifs, nommer l'apparence est correct et attendu — blue-500 doit décrire ce qu'il est, parce que c'est tout l'objet de la couche primitive. La règle « décrire l'intention » s'applique au-dessus d'elle.
Mise en œuvre avec les propriétés CSS personnalisées
Les propriétés CSS personnalisées sont le foyer naturel des tokens parce qu'elles cascadent et peuvent être surchargées par le contexte — ce qui est exactement le fonctionnement de la thématisation. Le motif consiste en deux couches de variables : les primitifs définis une fois à la racine, les sémantiques définis à la racine et redéfinis sous un sélecteur de thème.
Définissez les primitifs globalement — --blue-600: #2563EB, --gray-900: #111827, et ainsi de suite. Puis mappez les sémantiques vers eux : --color-action-primary: var(--blue-600) et --color-text-primary: var(--gray-900). Les composants ne lisent jamais que les variables sémantiques : color: var(--color-text-primary). Ils ne nomment jamais un primitif ni un code hexadécimal.
Dans Tailwind v4, cela se mappe proprement sur la nouvelle directive @theme, qui à la fois déclare les tokens et génère des utilitaires à partir d'eux, exposant chaque token comme une variable CSS d'exécution. Vous définissez --color-text-primary: var(--color-gray-900) à l'intérieur de @theme, vous utilisez text-primary dans le balisage, et vous surchargez la variable sous un sélecteur .dark dans votre couche de base. La documentation officielle des variables de thème Tailwind couvre la mécanique, et la même discipline à deux couches s'applique quel que soit le framework.
Le mode sombre est un problème de remappage de tokens, pas un problème de couleur
La raison pour laquelle la couche sémantique justifie son existence, c'est que le mode sombre devient presque trivial une fois qu'elle existe. Vous ne touchez à aucun composant. Vous surchargez les tokens sémantiques dans une portée .dark (ou [data-theme="dark"]) pour qu'ils se résolvent vers d'autres primitifs.
En mode clair, --color-bg-surface: var(--white) et --color-text-primary: var(--gray-900). En mode sombre, sous le sélecteur .dark, --color-bg-surface: var(--gray-900) et --color-text-primary: var(--gray-100). Chaque composant qui lit color-bg-surface bascule automatiquement, parce que la cascade fait le travail.
Deux choses qui font trébucher les gens ici :
- N'inversez pas simplement l'échelle. Du texte blanc pur sur noir pur (
#000sur#FFFinversé) est agressif et provoque de la halation — ce halo bavant autour du texte sur les écrans OLED. Les surfaces sombres devraient être un gris foncé comme#121212à#1A1A1A, et votre texte le plus clair un blanc cassé doux plutôt que#FFFFFF. C'est pourquoi votre échelle a besoin d'un vrai950et d'un vrai50plutôt que du noir et du blanc littéraux aux extrémités. - Désaturez les accents pour les fonds sombres. Un
blue-600qui paraît assuré sur blanc peut vibrer de façon inconfortable sur une surface quasi noire. Les thèmes sombres pointent généralement leur token d'action vers un palier plus clair et un peu moins saturé —blue-400au lieu deblue-600. Comme il s'agit d'un seul remappage de token, vous pouvez l'ajuster globalement en quelques secondes.
Les pièges qui cassent réellement à grande échelle
Après assez de changements de marque et de lancements de thème, les modes de défaillance riment :
- Laisser fuiter des primitifs dans les composants. Le péché originel. Faites-en une règle de lint — une règle qui interdit les noms de tokens primitifs et l'hexadécimal brut dans les fichiers de composants vaut la peine d'être écrite dès le premier jour.
- Des tokens sémantiques qui encodent secrètement une valeur.
color-blue-actionest un primitif déguisé en sémantique. Si le nom contient une teinte, il n'est pas vraiment sémantique, et il cassera au changement de marque. - Sauter la couche sémantique « pour aller vite ». Des composants liés directement aux primitifs paraissent acceptables jusqu'à votre premier thème. Vous refactorez alors sous la pression d'une échéance — le moment le plus coûteux pour le faire.
- Tester le contraste seulement en mode clair. Une paire qui passe le 4.5:1 sur blanc peut échouer sur une surface sombre, et inversement. Chaque paire sémantique texte-sur-arrière-plan doit être vérifiée dans chaque thème. Les seuils WCAG sont de 4.5:1 pour le texte normal et 3:1 pour le grand texte et les composants d'interface, selon les recommandations de contraste WCAG du W3C — et votre système de tokens est l'endroit où vous devriez ancrer ces garanties, pas celui où vous devriez découvrir des violations.
- Trop de tokens. Un système de quatre cents tokens sémantiques est aussi inutilisable qu'un système qui en a quatre. Si deux tokens se résolvent toujours vers la même valeur et le feront toujours, c'est qu'ils n'en font qu'un. N'ajoutez de la spécificité que lorsqu'une vraie divergence apparaît.
Le résumé honnête, c'est que les tokens ne prennent pas les décisions de couleur à votre place — ils rendent les décisions de couleur peu coûteuses à changer. C'est la structure qui vous achète la capacité de changer de marque un vendredi, de livrer le mode sombre en un sprint, et de corriger un bug de contraste en une ligne plutôt que dans quarante dépôts. Investissez votre énergie dans la couche sémantique et le nommage, gardez les composants honnêtes sur le niveau qu'ils touchent, et le système absorbera des changements que vous ne pouvez même pas encore prévoir.
Questions fréquentes
Quelle est la différence entre les tokens de couleur primitifs et sémantiques ?
Un token primitif (ou de base) nomme une valeur de couleur brute sans contexte — par exemple blue-500 : #3B82F6. Il décrit ce qu'est la couleur. Un token sémantique nomme le rôle de la couleur, comme color-action-primary ou color-feedback-error, et pointe vers un primitif. Les composants doivent référencer des tokens sémantiques, jamais des primitifs, afin qu'un changement de marque ou de thème ne nécessite que de repointer la couche sémantique plutôt que d'éditer chaque composant.
Comment nommer les tokens de couleur dans un design system ?
Nommez les tokens sémantiques et de composant par intention, pas par apparence. color-feedback-error survit à une refonte où le rouge d'erreur devient orange ; color-text-red, non. Utilisez une structure du général au spécifique comme catégorie-propriété-variante-état (par exemple color-bg-surface-raised, color-action-primary-hover). Les tokens primitifs font exception — ils doivent décrire la couleur littérale, comme gray-900, parce que c'est tout leur objet.
Pourquoi utiliser une échelle numérique de 50 à 950 ?
L'échelle 50-950 (50, 100, 200 … 900, 950) popularisée par Tailwind est devenue un standard de l'industrie, instantanément familier aux nouveaux développeurs. 50 est la teinte la plus claire, ~500 est la teinte de marque pure, et 950 est quasi noir. Espacer les paliers dans un modèle perceptuel comme HSL ou OKLCH — plutôt que de mélanger naïvement du blanc et du noir en sRGB — garde des paliers d'écart régulier et évite les demi-tons boueux.
Comment les tokens de couleur facilitent-ils le mode sombre ?
Le mode sombre devient un problème de remappage de tokens plutôt qu'une refonte. Comme les composants ne lisent que des tokens sémantiques, vous surchargez ces tokens dans une portée .dark ou [data-theme=dark] pour qu'ils se résolvent vers d'autres primitifs — par exemple color-bg-surface pointe vers le blanc en mode clair et vers gray-900 en mode sombre. La cascade CSS retourne automatiquement chaque composant, sans changer le code des composants. Pensez à désaturer les couleurs d'accent et à éviter les fonds noir pur pour réduire la fatigue oculaire et la halation.
Quels rapports de contraste les tokens de couleur doivent-ils respecter pour l'accessibilité ?
Les WCAG exigent un rapport de contraste d'au moins 4.5:1 pour le texte normal et 3:1 pour le grand texte et les composants d'interface. Intégrez ces vérifications à votre système de tokens en validant chaque paire texte-sur-arrière-plan sémantique dans chaque thème. Une combinaison qui passe en mode clair peut échouer en mode sombre, alors testez les deux, et définissez un token explicite texte-sur-action pour que les libellés sur les boutons saturés restent lisibles.
Envie d'expérimenter avec les couleurs ?
Essayez notre générateur de palettes gratuit pour trouver l'harmonie parfaite — avec un vérificateur de contraste WCAG intégré.
Ouvrir le générateur