Une bonne configuration de CSS dark mode repose sur trois éléments : des variables CSS qui gèrent les couleurs depuis un seul endroit, la media query prefers-color-scheme qui lit la préférence système de l'utilisateur, et un petit toggle pour que chacun puisse changer de thème manuellement. Reliez correctement ces trois pièces et le mode sombre devient cohérent, persistant et sans le scintillement (flicker) qui gâche tant de sites au chargement. Dans ce guide, je détaille sa construction de zéro, avec du code réel et fonctionnel.
Déplacer les couleurs dans des variables CSS
L'idée centrale d'un thème sombre est de pouvoir changer les valeurs de couleur sans toucher au HTML. La façon la plus propre d'y parvenir est de gérer chaque couleur via des propriétés personnalisées CSS déclarées sur :root. Ainsi vos composants référencent des noms abstraits, et la vraie valeur de couleur n'existe qu'à un seul endroit.
:root {
--bg: #ffffff;
--text: #1a1a1a;
--muted: #5a5a5a;
--surface: #f4f4f5;
--accent: #2563eb;
}
body {
background: var(--bg);
color: var(--text);
}
.card {
background: var(--surface);
border: 1px solid #e4e4e7;
}
Changer de thème revient désormais à mettre à jour la valeur de ces cinq variables. Vous n'avez plus jamais à modifier à la main des centaines de règles comme .card ou body une par une.
Lire la préférence système avec prefers-color-scheme
La plupart des utilisateurs ont déjà choisi un thème au niveau du système d'exploitation. La media query prefers-color-scheme capte exactement cette préférence. Votre page s'ouvre alors conformément au réglage de l'appareil, sans le moindre clic :
@media (prefers-color-scheme: dark) {
:root {
--bg: #0f0f10;
--text: #ededed;
--muted: #a1a1aa;
--surface: #1a1a1d;
--accent: #60a5fa;
}
}
Quelques points de vigilance. En mode sombre, utilisez un gris très foncé (comme #0f0f10) plutôt que du noir pur (#000) ; le texte blanc sur noir pur fatigue les yeux et provoque un flou appelé « halation ». De même, gardez un texte légèrement cassé plutôt que blanc pur. Pensez aussi à éclaircir les couleurs d'accent sur fond sombre ; un bleu profond superbe en mode clair paraît terne dans l'obscurité.
Ajouter un toggle manuel
La préférence système est un bon départ, mais les utilisateurs voudront toujours choisir le thème eux-mêmes. Pour cela, on écrit la préférence sous forme d'attribut data-theme sur l'élément html et on construit le CSS autour. Structurez d'abord le CSS pour trois états : le défaut (clair), le sombre issu du système et le sombre choisi manuellement.
/* Quand l'utilisateur a choisi sombre manuellement */
:root[data-theme="dark"] {
--bg: #0f0f10;
--text: #ededed;
--muted: #a1a1aa;
--surface: #1a1a1d;
--accent: #60a5fa;
}
/* Quand l'utilisateur a choisi clair, même si le système est sombre */
:root[data-theme="light"] {
--bg: #ffffff;
--text: #1a1a1a;
}
La subtilité : quand data-theme est absent, prefers-color-scheme garde la main ; dès que l'utilisateur a fait un choix, celui-ci prime sur le système. Côté JavaScript, il s'agit simplement de basculer cet attribut et de mémoriser le choix :
const btn = document.querySelector("#theme-toggle");
btn.addEventListener("click", () => {
const root = document.documentElement;
const current = root.getAttribute("data-theme");
// Déterminer le thème actuellement visible
const isDark = current
? current === "dark"
: matchMedia("(prefers-color-scheme: dark)").matches;
const next = isDark ? "light" : "dark";
root.setAttribute("data-theme", next);
localStorage.setItem("theme", next);
});
Éviter le scintillement (FOUC)
C'est l'erreur la plus fréquente : appliquer la préférence de thème une fois la page entièrement chargée. Dans ce cas, un utilisateur qui préfère le mode sombre voit un écran blanc pendant quelques millisecondes — c'est le FOUC (Flash of Unstyled Content), ou flicker de thème. La solution est d'appliquer la préférence stockée dans localStorage le plus tôt possible, dans le <head> de la page, avant que le moindre CSS ne soit peint.
<head>
<script>
(function () {
const saved = localStorage.getItem("theme");
if (saved) {
document.documentElement.setAttribute("data-theme", saved);
}
})();
</script>
<link rel="stylesheet" href="styles.css">
</head>
Comme ce petit script inline s'exécute avant le rendu, l'écran n'est jamais peint avec la mauvaise couleur. Placez toujours le script avant la feuille de style et directement dans le HTML ; déplacé dans un fichier externe, la latence réseau ramène aussitôt le flicker.
Adoucir les transitions — mais intelligemment
Un fondu de couleur doux au changement de thème est agréable, mais il faut être prudent. Appliquez transition à tout et vous risquez une animation indésirable au tout premier chargement. Une approche pratique consiste à n'activer la transition qu'au moment du basculement : le JavaScript ajoute une classe temporaire et la retire après un double requestAnimationFrame.
:root.theme-transition,
:root.theme-transition * {
transition: background-color .2s ease, color .2s ease;
}
Pour l'accessibilité, n'oubliez pas la préférence prefers-reduced-motion ; désactivez les transitions pour les utilisateurs qui veulent moins de mouvement. Ajoutez aussi un aria-label au bouton toggle afin que les lecteurs d'écran expliquent son rôle.
Tester et peaufiner
- Contraste : en mode sombre, gardez le contraste texte/fond au niveau WCAG AA (au moins 4,5:1). L'outil de contraste des DevTools du navigateur l'affiche instantanément.
- Images : les logos PNG transparents peuvent disparaître sur fond sombre ; servez une variante dédiée au mode sombre si besoin.
- Ombres : les ombres portées sont presque invisibles en mode sombre. Rendez la profondeur avec des couleurs de surface légèrement plus claires plutôt qu'avec des ombres.
- theme-color : sur mobile, accordez la couleur de la barre du navigateur au thème avec
<meta name="theme-color">.
Questions fréquentes
prefers-color-scheme fonctionne-t-il dans tous les navigateurs ?
Oui — tous les grands navigateurs actuels (Chrome, Firefox, Safari, Edge) le supportent depuis des années. Sur un très vieux navigateur, la media query est simplement ignorée et l'utilisateur voit le thème par défaut (clair) ; cela fonctionne donc sans risque comme amélioration progressive.
Pourquoi stocker la préférence dans localStorage plutôt qu'un cookie ?
Le thème est une préférence visuelle purement côté client ; inutile de l'envoyer au serveur. localStorage se lit de façon synchrone, donc le script inline dans votre <head> peut le récupérer immédiatement et éviter le flicker. Si vous voulez un thème rendu côté serveur, un cookie a plus de sens, car il est attaché à la requête.
prefers-color-scheme ne suffit-il pas — un toggle est-il vraiment nécessaire ?
Ce n'est pas strictement nécessaire, mais c'est recommandé. Certains utilisateurs règlent leur appareil en sombre mais veulent lire certains sites en clair (ou l'inverse). Un toggle manuel leur donne ce contrôle et améliore la satisfaction.
Un thème sombre propre et sans flicker est à la fois élégant et reposant pour les yeux de vos utilisateurs. Si vous voulez mettre en place le mode sombre sur votre site, migrer un système de couleurs existant vers des variables ou corriger vos transitions de thème, contactez-moi — concrétisons-le rapidement ensemble.