Aller au contenu

Le média des artisans du web mercredi 19 août 2026

Design & UX

Design tokens en CSS : la méthode pour un thème clair/sombre maintenable

Deux couches de tokens, une redéfinition sémantique et trois lignes de script : la méthode qui évite les fichiers dark.css qui divergent et les !important en cascade.

Design tokens en CSS : ma méthode pour un thème clair/sombre maintenable

Les thèmes sombres bricolés après coup laissent une trace reconnaissable : une variable ici, un !important là, un fichier dark.css dupliqué qui diverge du thème clair au fil des sprints. Le problème n’est pas la couleur, c’est l’absence de structure. Les design tokens en CSS custom properties apportent cette structure, sans dépendance ni couche JavaScript.

Un token n’est pas une couleur

La confusion la plus répandue consiste à assimiler une valeur brute (#1e40af) à un token. Un design token est une intention nommée : « la couleur du texte principal », et non « du bleu foncé ». D’où une organisation en deux couches. La couche primitive contient les valeurs brutes, jamais utilisées directement par les composants. La couche sémantique contient les intentions, et c’est la seule que les sélecteurs manipulent.

:root {
  /* Couche 1 : primitives (jamais utilisées telles quelles) */
  --gray-900: #111418;
  --gray-50:  #f7f8fa;
  --blue-500: #2563eb;
  --blue-300: #93c5fd;

  /* Couche 2 : sémantique (l'API des composants) */
  --color-bg:      var(--gray-50);
  --color-text:    var(--gray-900);
  --color-accent:  var(--blue-500);
}

Le bénéfice est immédiat en maintenance : un changement de teinte de marque se traite dans les primitives, un problème de contraste détecté en audit d’accessibilité se traite dans la couche sémantique. Les composants, eux, ne bougent pas — ils consomment var(--color-text).

Un composant qui référence une couleur brute est une dette technique qui s’ignore. Un composant qui référence un token sémantique est un contrat.

Le thème sombre comme redéfinition

Une fois cette séparation en place, le mode sombre devient une opération sans relief — ce qui est précisément l’objectif. Rien n’est dupliqué : seule la couche sémantique est redéfinie. Les primitives restent, les composants restent, seule la table de correspondance change.

1. Respecter la préférence système

Le point de départ est prefers-color-scheme, qui traduit la préférence exprimée par l’utilisateur au niveau de son système d’exploitation. L’ignorer revient à passer outre un réglage explicite.

@media (prefers-color-scheme: dark) {
  :root {
    --color-bg:     var(--gray-900);
    --color-text:   var(--gray-50);
    --color-accent: var(--blue-300);
  }
}

2. Autoriser un choix explicite

Un attribut posé sur l’élément racine permet de surcharger la préférence système lorsque l’utilisateur bascule manuellement. L’ordre des règles détermine la priorité : le choix explicite doit l’emporter dans les deux sens.

:root[data-theme='dark'] { /* mêmes redéfinitions */ }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) { /* mêmes redéfinitions */ }
}

3. Éviter le flash au chargement

La seule ligne de JavaScript réellement nécessaire est un script bloquant placé dans le <head>, qui applique le thème mémorisé avant le premier rendu. Toute autre approche produit un flash de thème clair sur les pages chargées en mode sombre.

<script>
  try {
    var t = localStorage.getItem('theme');
    if (t) document.documentElement.setAttribute('data-theme', t);
  } catch (e) {}
</script>
Point de vigilance : déclarer color-scheme: light ou dark sur :root ne relève pas du détail. C’est ce qui indique au navigateur d’adapter les contrôles de formulaire, les barres de défilement et les champs natifs. Sans cette déclaration, un thème sombre reste trahi par ses <select>.

Ce que la méthode évite

Symptôme fréquentCauseRéponse par les tokens
Fichier dark.css qui divergeDuplication de règlesUne seule feuille, redéfinition sémantique
Accumulation de !importantConflits de spécificitéLes composants ne lisent qu’une variable
Contraste insuffisant en sombreCouleurs choisies au cas par casCorrection centralisée sur un token
Flash clair au chargementThème appliqué après le renduScript bloquant dans le <head>

Ce qu’il faut retenir

Un thème clair/sombre maintenable ne demande ni framework ni couche JavaScript : deux couches de tokens, une redéfinition sémantique, et un script de trois lignes contre le flash initial. L’essentiel du travail est une décision d’architecture prise avant d’écrire la première règle de style.

C’est la méthode que j’applique sur mes projets clients depuis plusieurs années, et le thème de ce site en est une application directe. La règle que je ne transige plus : aucun composant ne référence une primitive. Le jour où cette règle saute, la dette revient en six mois. — Simon Janvier

À lire aussi sur Mail Studio

Partager LinkedIn Bluesky Hacker News E-mail

À lire aussi