Dark mode en CSS : le guide complet 2026
Maîtrisez le dark mode en CSS en 2026 : prefers-color-scheme, variables, color-scheme, light-dark() et toggle manuel avec exemples de code et accessibilité.
Le dark mode en CSS est passé d’un gadget esthétique à une attente standard des utilisateurs. En 2026, implémenter un thème sombre propre, accessible et sans clignotement repose sur quelques primitives natives du langage : la media feature prefers-color-scheme, les variables CSS pour le theming, la propriété color-scheme et, désormais largement disponible, la fonction light-dark(). Ce guide technique détaille chaque approche avec du code réel, leurs pièges, et la manière de respecter les contraintes d’accessibilité WCAG.
Réponse directe : pour gérer le dark mode en CSS, utilisez prefers-color-scheme pour suivre la préférence de l’OS, des variables CSS (custom properties) pour centraliser vos couleurs, et déclarez color-scheme pour adapter les contrôles natifs. Ajoutez ensuite un toggle manuel via un attribut data-theme combiné à la fonction light-dark() pour laisser l’utilisateur forcer son choix.
Suivre l’OS avec prefers-color-scheme
La base historique du dark mode est la media feature prefers-color-scheme. Elle détecte la préférence exprimée par l’utilisateur au niveau du système d’exploitation ou du navigateur, et expose deux valeurs principales : light et dark. D’après MDN, la bonne pratique consiste à définir un thème clair par défaut, puis à surcharger les couleurs en mode sombre.
:root {
--bg: #ffffff;
--text: #1a1a1a;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #121212;
--text: #e0e0e0;
}
}
body {
background: var(--bg);
color: var(--text);
}
Cette approche fonctionne dans tous les navigateurs modernes depuis plusieurs années et constitue le socle minimal d’un dark mode automatique. Son inconvénient : elle ne permet pas, seule, de proposer un bouton de bascule manuel.
Centraliser les couleurs avec les variables CSS
Le theming efficace repose sur les custom properties. Plutôt que de répéter des codes hexadécimaux partout, vous définissez un jeu de tokens sémantiques (--surface, --text, --accent) et vous ne changez que leurs valeurs selon le thème. C’est le principe que nous détaillons aussi dans notre article sur la gestion de la cascade avec la spécification CSS Layer, particulièrement utile pour organiser des thèmes complexes.
:root {
--surface: #ffffff;
--surface-elevated: #f5f5f5;
--text: #1a1a1a;
--text-muted: #555555;
--accent: #0066cc;
--border: #e0e0e0;
}
[data-theme="dark"] {
--surface: #121212;
--surface-elevated: #1e1e1e;
--text: #e0e0e0;
--text-muted: #a0a0a0;
--accent: #4d9fff;
--border: #2a2a2a;
}
L’isolation de ces tokens peut être renforcée avec des techniques de portée modernes ; voyez à ce sujet notre guide sur la règle @scope pour des styles locaux.
Déclarer color-scheme pour les contrôles natifs
Définir des variables ne suffit pas : les éléments fournis par le navigateur (barres de défilement, champs de formulaire, correcteur orthographique, surface du canvas) ne s’adaptent pas automatiquement. La propriété color-scheme règle ce problème. Selon MDN, elle indique au user agent quels schémas un élément peut afficher confortablement.
:root {
color-scheme: light dark;
}
[data-theme="light"] { color-scheme: light; }
[data-theme="dark"] { color-scheme: dark; }
MDN recommande aussi d’ajouter la balise meta <meta name="color-scheme" content="light dark"> dans le <head>, avant tout CSS, pour informer le navigateur le plus tôt possible et éviter un flash de fond clair au chargement.

La fonction light-dark() : le standard 2026
La grande nouveauté qui simplifie tout est la fonction light-dark(). Elle accepte deux valeurs — la première pour le thème clair, la seconde pour le sombre — et renvoie la bonne selon le schéma actif, sans media query. D’après MDN, elle dépend de la valeur calculée de color-scheme pour décider quelle couleur appliquer.
:root {
color-scheme: light dark;
}
body {
background: light-dark(#ffffff, #121212);
color: light-dark(#1a1a1a, #e0e0e0);
}
.card {
background: light-dark(#f5f5f5, #1e1e1e);
border: 1px solid light-dark(#e0e0e0, #2a2a2a);
}
Côté support, la page web-features indique que light-dark() est Baseline « Newly available » depuis le 13 mai 2024 et atteindra le statut Baseline « Widely available » le 13 novembre 2026. Concrètement, elle est implémentée dans Chrome 123, Edge 123, Firefox 120 et Safari 17.5. Can I Use confirme une couverture d’environ 95 % mi-2026 ; prévoyez un fallback pour les navigateurs anciens.
/* Fallback : couleur unique d'abord, light-dark() ensuite */
body {
background: #ffffff;
background: light-dark(#ffffff, #121212);
}
Attention à un piège subtil documenté par Stefan Judis : light-dark() se base sur color-scheme, pas directement sur prefers-color-scheme. Si vous forcez color-scheme: dark via un toggle, light-dark() suivra le toggle — c’est précisément ce qui le rend si pratique pour la bascule manuelle.
Comparatif des approches
| Approche | Pour | Contre |
|---|---|---|
@media (prefers-color-scheme) | Support universel, automatique selon l’OS | Pas de toggle manuel ; duplication des couleurs |
Variables CSS + data-theme | Theming centralisé, toggle manuel possible | Nécessite un peu de JS pour basculer |
color-scheme | Adapte scrollbars et formulaires natifs | Ne change pas vos couleurs custom à lui seul |
light-dark() | Syntaxe concise, suit le toggle via color-scheme | Fallback requis pour vieux navigateurs |
Implémenter un toggle manuel persistant
Pour offrir trois états (système, clair, sombre), on combine un attribut data-theme sur <html>, une persistance en localStorage, et light-dark() qui suit color-scheme. Le JavaScript reste minimal.
<button id="theme-toggle" aria-label="Changer de thème">Thème</button>
const root = document.documentElement;
const saved = localStorage.getItem('theme');
if (saved) root.dataset.theme = saved;
document.getElementById('theme-toggle').addEventListener('click', () => {
const next = root.dataset.theme === 'dark' ? 'light' : 'dark';
root.dataset.theme = next;
localStorage.setItem('theme', next);
});
html { color-scheme: light dark; } /* défaut = OS */
html[data-theme="light"] { color-scheme: light; }
html[data-theme="dark"] { color-scheme: dark; }
body {
background: light-dark(#ffffff, #121212);
color: light-dark(#1a1a1a, #e0e0e0);
}
L’élégance ici : changer color-scheme suffit, light-dark() recalcule automatiquement toutes les couleurs. Pour des interactions plus riches autour de ce bouton, nos articles sur le sélecteur :has() en CSS et les animations CSS au scroll en 2026 montrent des patterns complémentaires sans JavaScript lourd.
Éviter le flash de thème (FOUC)
Le piège le plus visible est le « flash » : la page s’affiche brièvement en clair avant que le JS n’applique le thème sombre sauvegardé. La parade consiste à exécuter un petit script bloquant dans le <head>, avant le rendu :
<script>
const t = localStorage.getItem('theme');
if (t) document.documentElement.dataset.theme = t;
</script>
Placé tout en haut du <head>, il s’exécute avant que le navigateur ne peigne la page, éliminant le clignotement. Couplé à la balise meta color-scheme, cela couvre aussi la couleur de fond du canvas pendant le chargement initial.
Gérer les images et les SVG
Les médias demandent un traitement spécifique. Pour les SVG inline, exploitez currentColor ou des variables CSS afin que les icônes héritent du thème. Pour les images bitmap, servez deux versions via <picture> :
<picture>
<source srcset="/img/hero-dark.webp" media="(prefers-color-scheme: dark)">
<img src="/img/hero-light.webp" alt="Aperçu du tableau de bord" width="800" height="450">
</picture>
Pensez aussi à atténuer les images très lumineuses en mode sombre avec un léger filter: brightness(.85). Cette logique de bascule conditionnelle se marie bien avec une mise en page adaptative ; voir notre guide sur les container queries 2.0.
Contraste et accessibilité (WCAG)
Un dark mode doit rester lisible. Les WCAG 2.2 imposent un ratio de contraste d’au moins 4.5:1 pour le texte normal et 3:1 pour le texte large et les composants d’interface — et ce, indépendamment dans chaque thème.
Évitez le piège du blanc pur sur noir pur. Comme l’explique WebAIM, #ffffff sur #000000 atteint certes 21:1, mais provoque un effet de halation (texte qui « brille ») gênant pour les personnes astigmates. Préférez un texte cassé (#e0e0e0) sur un fond gris très sombre (#121212) : le contraste reste d’environ 13:1, largement conforme, tout en étant plus confortable pour les yeux. Testez systématiquement vos paires de couleurs avec un vérificateur de contraste.
[data-theme="dark"] {
--surface: #121212; /* pas #000 */
--text: #e0e0e0; /* pas #fff */
}
Questions fréquentes
Faut-il encore prefers-color-scheme si j’utilise light-dark() ?
Pas pour les couleurs : light-dark() gère les deux thèmes via color-scheme. Mais prefers-color-scheme reste utile pour basculer des images dans <picture>, des ressources externes, ou comme valeur initiale détectée par votre script de toggle au premier chargement.
light-dark() est-il prêt pour la production en 2026 ?
Oui. Selon Can I Use, le support dépasse 95 % et la fonction atteint Baseline « Widely available » le 13 novembre 2026. Pour les navigateurs plus anciens, déclarez une couleur unique avant l’appel light-dark() : la cascade CSS appliquera le fallback automatiquement.
Comment éviter le flash de thème clair au chargement ?
Exécutez un script inline et bloquant en tout début de <head> qui lit localStorage et pose l’attribut data-theme avant le premier rendu. Ajoutez aussi <meta name="color-scheme" content="light dark"> pour que le fond du navigateur s’adapte immédiatement.
Quelles couleurs choisir pour un dark mode accessible ?
Évitez le noir et le blanc purs. Utilisez un fond gris très sombre (#121212 à #1e1e1e) et un texte cassé (#e0e0e0 à #f0f0f0). Vérifiez un ratio d’au moins 4.5:1 pour le texte normal afin de respecter les WCAG 2.2 dans les deux thèmes.
Dois-je proposer un toggle ou laisser l’OS décider ?
L’idéal combine les deux : suivez l’OS par défaut via color-scheme: light dark, puis offrez un bouton permettant de forcer un thème. Persistez le choix dans localStorage. Cela respecte la préférence système tout en laissant le contrôle final à l’utilisateur.
Conclusion
En 2026, le dark mode en CSS s’appuie sur une pile claire : prefers-color-scheme pour l’automatisme, des variables CSS pour centraliser les tokens, color-scheme pour les contrôles natifs, et light-dark() pour une syntaxe concise qui suit votre toggle manuel. Ajoutez un script anti-flash, des médias adaptatifs et un contraste conforme aux WCAG, et vous obtiendrez un thème robuste et accessible. Pour aller plus loin dans une stack moderne, comparez ces primitives natives avec les utilitaires dans notre dossier Tailwind vs CSS moderne en 2026.