Aller au contenu principal
Site en cours de refonte — quelques pages peuvent bouger ou évoluer.
07 avril 2026

J’ai généré toute ma palette depuis 1 hex… et j’ai compris pourquoi mon dark mode était moche

Tu veux une UI propre sans passer ta semaine à tweaker des nuances. OK, mais si tu génères une palette sans comprendre OKLCH, ton dark mode va te punir.

10 min de lecture
22 vues
réactions
Partager :
J’ai généré toute ma palette depuis 1 hex… et j’ai compris pourquoi mon dark mode était moche

Si tu cherches « générer une palette OKLCH depuis une couleur » pour obtenir des design tokens CSS utilisables en light/dark, tu es au bon endroit. Je l’ai fait, j’ai adoré le gain de temps… et j’ai aussi compris un truc : le générateur n’est pas le problème. Le problème, c’est quand tu lui demandes de compenser des choix que tu n’as pas faits (surfaces, contrastes, états, neutralité des gris). C’est exactement comme ça qu’on se retrouve avec un dark mode “techniquement ok” mais visuellement triste, boueux, ou carrément agressif.

Pourquoi OKLCH évite les dégradés moches (et pourquoi ton dark mode te trahit)

La promesse d’OKLCH, c’est simple à formuler et très visible à l’écran : quand tu changes la lightness (L) d’une couleur, tu as beaucoup plus de chances d’obtenir une progression qui “fait sens” visuellement. Là où RGB et même HSL te donnent vite des paliers chelous, des zones qui se désaturent d’un coup, ou des teintes qui partent en vrille, OKLCH est pensé pour coller à la perception humaine.

Dans un dark mode, ce détail devient violent. Beaucoup de palettes “générées” se contentent d’inverser l’échelle ou de réduire la lumière en gardant le reste plus ou moins constant. Résultat : tu te retrouves avec des fonds qui tirent vers le gris sale, des accents qui crient, et surtout des contrastes qui semblent bons sur le papier mais qui fatiguent en lecture réelle. OKLCH aide parce que tu peux piloter proprement trois choses séparées : la luminosité, la saturation (chroma) et la teinte.

Le point que j’aurais aimé qu’on me dise plus tôt : en dark mode, le “beau” vient rarement d’un simple miroir du light. Tu as besoin d’une stratégie de surfaces (fond, carte, overlay, input), et d’une façon de gérer la chroma pour éviter les couleurs “néon sur bitume”. OKLCH te donne les bons boutons. Mais il faut quand même les tourner.

De 1 hex à une palette utilisable : ce que fait vraiment un générateur OKLCH

Les générateurs dont on parle en ce moment (et qu’on voit passer sur r/css) partent souvent d’un seul hex “brand” et dérivent une échelle complète. Le principe est à peu près toujours le même : tu convertis ton hex en OKLCH, tu fixes une teinte (h) cohérente, puis tu construis une série de nuances en faisant varier la lightness (L) et en modulant la chroma (C) pour que ça reste dans le gamut et que ça ne devienne pas dégueu sur certaines marches.

Le vrai boulot n’est pas de “créer 10 couleurs”. Le vrai boulot, c’est d’éviter les deux pièges classiques : les nuances qui sortent du gamut (tu demandes une couleur que l’écran ne peut pas afficher, donc ça clippe et ça change la teinte), et les nuances qui gardent trop de chroma quand tu t’approches du noir (ça fait vite du fluo ou du sale selon la teinte de départ). Un bon générateur fait des compromis à ta place : il limite la chroma, il recadre la lightness, il essaie de garder une progression régulière.

Mais attention au raccourci mental. Ce que tu obtiens, ce n’est pas “un design system”. C’est une matière première. Ça peut être excellent, et ça peut te faire gagner des heures. Sauf que dès que tu poses une UI dessus, tu vas découvrir ce qui manque : des neutres dignes de ce nom, des tokens sémantiques, et des états interactifs pensés pour les surfaces réelles.

Le truc qui change tout : séparer tokens « bruts » et tokens sémantiques

Si tu fais une seule couche de variables du style --blue-500 et que tu les utilises partout, tu viens de t’enfermer. Ça marche pour une maquette. En prod, ça devient une dette : tu ne peux plus ajuster un contraste ou une surface sans casser la moitié des composants.

Ce que je fais maintenant, c’est deux niveaux. D’abord une palette “raw” générée (tes marches 50–950, peu importe comment tu les nommes). Ensuite, une couche de tokens sémantiques : --color-bg, --color-surface, --color-text, --color-muted, --color-primary, --color-primary-contrast, --color-border, etc. C’est cette couche-là que tes composants consomment. La palette brute n’est qu’un réservoir.

Et c’est précisément là que le dark mode devient “propre”. Tu ne cherches pas à inverser une échelle. Tu redéfinis des intentions.

:root {
  /* Palette brute (exemple) */
  --brand-50:  oklch(0.97 0.02 250);
  --brand-100: oklch(0.93 0.04 250);
  --brand-200: oklch(0.88 0.07 250);
  --brand-300: oklch(0.82 0.10 250);
  --brand-400: oklch(0.74 0.14 250);
  --brand-500: oklch(0.66 0.17 250);
  --brand-600: oklch(0.58 0.16 250);
  --brand-700: oklch(0.50 0.14 250);
  --brand-800: oklch(0.42 0.12 250);
  --brand-900: oklch(0.34 0.10 250);

  /* Tokens sémantiques (light) */
  --color-bg: oklch(0.99 0.00 0);
  --color-surface: oklch(0.97 0.00 0);
  --color-text: oklch(0.20 0.02 260);
  --color-muted: oklch(0.55 0.02 260);
  --color-border: oklch(0.88 0.01 260);

  --color-primary: var(--brand-600);
  --color-primary-hover: var(--brand-700);
  --color-primary-contrast: oklch(0.98 0.00 0);
  --color-focus: oklch(0.72 0.16 250);
}

[data-theme="dark"] {
  /* Tokens sémantiques (dark) */
  --color-bg: oklch(0.14 0.01 260);
  --color-surface: oklch(0.18 0.01 260);
  --color-text: oklch(0.92 0.01 260);
  --color-muted: oklch(0.70 0.02 260);
  --color-border: oklch(0.30 0.01 260);

  --color-primary: var(--brand-400);
  --color-primary-hover: var(--brand-300);
  --color-primary-contrast: oklch(0.15 0.01 260);
  --color-focus: oklch(0.78 0.14 250);
}

Tu vois l’idée : en dark, je n’essaie pas d’utiliser “les mêmes numéros inversés”. Je choisis un primary plus clair (pour ressortir sur fond sombre), je garde le texte très lisible, et je règle les surfaces comme un système. Ça paraît évident après coup. Avant, je me faisais piéger par une palette “mathématiquement jolie” mais UI-fausse.

Automatique ≠ magique : contraste WCAG, états et surfaces, là où tout casse

Les générateurs aiment bien dire « on valide WCAG ». C’est cool, vraiment. Sauf que WCAG ne valide pas ton design, il valide des paires de couleurs sur un ratio de contraste. Et en prod, tu n’as pas “texte sur fond” et basta. Tu as des boutons sur des surfaces, des badges sur des cartes, du texte muted dans un tableau, des placeholders, des inputs focus, des liens visités, des états disabled, des toasts, des graphes… et tout ce petit monde se superpose.

Le piège le plus fréquent en dark mode, c’est de pousser les contrastes trop haut partout. Tu “passes WCAG” à la mitraillette, tu te retrouves avec un écran qui scintille. Le texte principal doit être nickel, oui. Mais les bordures, les séparateurs, les textes secondaires ont besoin d’être moins contrastés, sinon tu perds la hiérarchie et tu fatigues l’œil. À l’inverse, le focus ring doit être très clair et très fiable, surtout au clavier. Et ça, un générateur ne le devine pas, parce que ça dépend de tes composants.

Autre point terrain : ne te force pas à tout dériver de la couleur brand. Tu peux avoir une palette d’accent OKLCH très propre, et à côté un set de neutres (gris) construit séparément. C’est souvent ce qui “débloque” un dark mode. Les neutres pilotent l’ambiance et la lisibilité. L’accent sert à guider. Si tu te retrouves avec des fonds bleutés parce que ton brand est bleu et que tout en découle, tu vas vite détester ton propre produit.

Tester vite (et bien) : ta palette doit survivre à des pages réelles

Je ne teste plus une palette dans un petit carré de couleur. Je teste dans deux ou trois pages de référence qui ressemblent à de la prod : une page “forms” (inputs, selects, erreurs, helper text), une page “content” (titres, paragraphes, liens, code inline), et une page “dashboard” (table, badges, cards, graphiques si tu en as). Tu changes une variable, tu vois immédiatement si tu as cassé la hiérarchie ou si tu as un hover trop violent.

Si tu veux aller vite, fais-toi un mini playground interne et garde-le dans le repo. C’est bête, mais ça évite de juger une couleur hors contexte. Et si tu peux, fais un test sur au moins deux écrans différents. Certaines couleurs qui semblent “fine” sur un MacBook lumineux deviennent boueuses sur un écran moyen. Le dark mode est impitoyable là-dessus.

Dernier truc qui m’a déjà sauvé : une capture avant/après et un diff visuel (même à la main). Ton cerveau s’habitue vite à une UI moche. Le diff te remet une claque quand tu viens de rendre des bordures invisibles ou des textes secondaires trop présents.

Comparer et mapper sur Tailwind sans te perdre dans 50 teintes

Tailwind est pratique parce qu’il impose une échelle (souvent 50 à 950). Le problème, c’est que tu peux te perdre en essayant de “matcher” exactement une palette Tailwind existante, surtout si tu passes ta journée à scroller des nuanciers. J’aime bien l’approche inverse : je garde ma logique OKLCH et je mappe juste quelques correspondances mentales. Typiquement, je veux savoir quelle marche correspond à mon primary “action”, laquelle sert à un hover, et lesquelles servent à des backgrounds très légers ou à des contours.

Le genre d’outil vu sur r/tailwindcss, qui prévisualise les couleurs dans une landing “réelle”, aide énormément. Parce que tu ne choisis pas une couleur pour sa beauté intrinsèque. Tu la choisis parce qu’elle fonctionne sur un bouton, un lien, un badge, avec du texte dessus. Et ça, une grille de swatches ne te le montre pas.

Si tu utilises Tailwind, le combo le plus sain que j’ai trouvé, c’est : Tailwind pour la mise en page et le rythme, et des couleurs pilotées par des variables CSS sémantiques. Comme ça, tu ne refais pas ton code quand tu ajustes la palette. Tu ajustes les tokens.

// tailwind.config.js (idée générale)
export default {
  theme: {
    extend: {
      colors: {
        bg: "oklch(var(--color-bg) / <alpha-value>)",
        surface: "oklch(var(--color-surface) / <alpha-value>)",
        text: "oklch(var(--color-text) / <alpha-value>)",
        primary: "oklch(var(--color-primary) / <alpha-value>)",
      }
    }
  }
}

Ce mapping-là évite le piège des 50 teintes “utilisées un peu au hasard”. Ton UI devient plus cohérente, et tes changements deviennent localisés. C’est exactement ce qu’on veut quand on itère en prod.

Mon retour après l’avoir fait pour de vrai : la palette n’est pas ton design system

Générer une palette OKLCH depuis un hex, c’est un super accélérateur. Je le referai sans hésiter. Mais si ton dark mode est moche, c’est rarement parce que « la palette est mauvaise ». C’est parce que tu n’as pas posé un système de surfaces, parce que tes neutres sont bancals, parce que tu utilises des tokens bruts dans les composants, ou parce que tu as sur-optimisé le contraste partout.

La bonne nouvelle, c’est que ça se corrige vite quand tu as la bonne structure. Une fois que tes composants consomment des tokens sémantiques, tu peux faire les ajustements qui comptent sans “tout casser”. Et c’est là que la génération devient vraiment utile : tu passes moins de temps à peindre, plus de temps à rendre l’interface lisible et agréable.

Sources

Cet article vous a plu ?

Commentaires

Laisser un commentaire

Entre 10 et 2000 caractères

Les commentaires sont modérés avant publication.

Aucun commentaire pour le moment.

Soyez le premier à donner votre avis !