Dark mode: un guide pour passer du côté obscur

#react
#css
#nextjs

Le dark mode est sur la liste des features que je voulais implémenter sur mon site depuis longtemps. Je n'y connaissais rien et j'ai appris quelques trucs. Tl;dr : on ne va pas faire du filter:invert(1).


Ça fait un moment que je cherche à remplacer le Tailwind que j'ai sur ce blog, par une librairie de composants React performante, facile à utiliser et qui m’aide un peu sur certaines features. Comme j’utilise déjà Antd et ChakraUI au taf, je voulais essayer autre chose. J'ai vraiment beaucoup aimé Mantine sur mon calendrier de l’Avent (vous pouvez toujours y aller ici si vous ne l’avez pas vu, je le fais repartir chaque mois jusqu’au nouveau 2025), et ai donc décidé de migrer mon blog sur Mantine aussi.

Bref, c’est une grande introduction pour dire que cette migration m’a emmenée dans une partie que j’avais encore peu explorée en front : le dark mode/thème. En effet, Mantine (comme pas mal de bibliothèques de composants React) fournit des hooks et un système de thèmes qui rendent l'implémentation basique du dark mode vraiment simple. Je l’ai fait assez facilement sur le calendrier de l’Avent et je me suis dit, “go sur le blog”, ça va se faire en 2-2.

Oupsie. Pas du tout.

Britney did it again

Britney did it again.

Si implémenter un dark mode depuis 0 sur un site de 2-3 pages, ça se fait les doigts dans le nez avec un bon framework, quand le site est déjà en place (et donc son style, voire sa charte graphique s’il y en a une), c’est beaucoup plus galère.

Je reviens donc ici sur les galères que j’ai rencontrées et, au final, comment on fait pour limiter les dégâts, quand on n’y connait rien en dark mode.

Parce que je n’y connaissais rien : dédicace à tous les gens qui pensent que c’est juste un fond noir à la place du fond blanc. Venez avec moi, la suite va vous étonner.

Pourquoi du dark mode ?

Ben oui, pourquoi ne pas rester dans la lumière en fait ? D’où ça vient de vouloir passer de l’autre côté ? Promis cette parenthèse historico-socio-théorique ne sera pas longue, ne scroll pas, ça vaut le coup.

Le dark mode, en bref, c’est la possibilité d’avoir une interface à fond foncé plutôt que clair, comme on la trouve majoritairement aujourd’hui sur nos apps et sites. C’est intéressant, car la lumière n’a pas toujours été la norme : quand on pense aux première interfaces d’ordinateur, on voit plutôt du noir ou le fameux bleu foncé.

Illustration d'une fenêtre d'installation sous DOS, bleue et grise.

Peu de fond blanc dans l'UX Windows de 1998.

Peu à peu pour des raisons que vous pourrez retrouver ailleurs car ce n'est pas le sujet ici, le fond clair est devenu la norme, voire une quasi obligation. Le dark mode n’était jamais totalement parti cependant et, depuis quelques années, on observe une recrudescence, pour plusieurs raisons.

  1. Tout d’abord, et c’est une des principales raisons avancées, pour l’économie-écologie : dans les cas des écrans OLED ou AMOLED, les pixels noirs ne consomment pas d’énergie, ce qui peut réduire considérablement l’usage de la batterie si on replace les pixels blancs par des pixels noirs.
  2. Mais aussi, car les fonds blancs aux forts contrastes peuvent être très agressifs pour les yeux, notamment si on fait un usage de l’écran de nuit/dans un endroit sombre ou dans le cas de certains handicaps. Avec les smartphones, les usages des écrans dans des endroits sombres ou de nuit ont considérablement augmenté et donc le besoin de dark mode aussi. La prise en compte de l’accessibilité des écrans a aussi augmenté et par conséquent le fait de proposer un dark mode.
  3. Certaines fonctionnalités ne se prêtent cependant pas au noir, pour des raisons aussi bien psychologiques (avoir son app bancaire en noir, ça peut faire peur), que de lisibilité : les graphiques et charts en dark mode peuvent par exemple avoir un rendu moyen comme l’ont montré Erickson, Kim et Brueger dans leur article consacré au sujet. Par contre pour les sites sur lesquels les utilisateurs.trices restent “longtemps”, lisent beaucoup, le dark mode est clairement un avantage.
  4. En matière de chiffres : plusieurs sites font régulièrement des études de l’usage du dark mode par leurs users. En survolant quelques chiffres, je les ai trouvés assez cohérents entre les plateformes : cela tourne toujours autour d'une utilisation de 20-25% du thème sombre pour le web (j’ai notamment regardé les chiffres de Chrome platform status et découvert par la même occasion ce site fascinant). Par contre, quand on se tourne vers le téléphone, les chiffres sont incroyablement différents : 81% des utilisateurs d’Android utiliseraient leur téléphone en dark mode (c’est à peu près le même chiffre pour Apple). En termes d’usage, c’est plus compliqué, mais feu Twitter révélait par exemple que les users utilisant le dark mode restaient plus longtemps sur sa plateforme (est-ce qu’il y a une causalité, mystère).
  5. Parce que certaines personnes préfèrent le mode sombre et qu’on tient de plus en plus compte des préférences des utilisateurs.trices. Finalement, si quelqu’un le préfère et qu'on peut le fournir, autant le faire, non ?

Alors, comment fait-on un bon dark mode ?

Partant de ces points, je me suis imposé quelques règles pour implémenter un premier jet de dark mode simple. Ces règles se découpent en trois grands chapitres : usage, design et technique.

Le + de Dre Drey

Chapitre 1. L’usage : switcher de partout, tout le temps

Même si les stats montrent que les users se mettent davantage en dark mode par habitude que parce que “wow, ce site est trop stylé en noir”, le dark mode n’est pour autant pas adapté à tous les contenus ni à tout le monde. Même si certains sites/app ont fait le choix d’un only dark mode (sans surprise les produits de divertissement comme Netflix, Spotify), j’ai choisi de considérer le dark mode aussi bien comme un choix définitif de l’user (les “dark thinkers” qui vont être en dark mode tout le temps parce que “dark mode is cool”) que comme un switch temporaire, selon la forme du jour, le contenu du site, la luminosité extérieure, etc. L’user doit donc pouvoir changer facilement le thème, partout, n’importe quand, sur n'importe quelle page.

En conclusion, on n’invente pas la roue : le dark mode se switch de préférence dans la barre de navigation ou, a minima, depuis un endroit facilement accessible depuis n'importe quelle page de votre site.

L’autre option, c’est bien sûr d’avoir le dark mode dans les paramètres utilisateurs. En dehors du fait que, selon moi, cette option ne permet pas la flexibilité recherchée par certains utilisateurs, notamment sur le web, elle pose en plus la question du type de stockage de cette préférence : en local storage ? Dans les cookies ? En base ? J’en parle davantage après dans les considérations techniques.

Chapitre 2. Le design : dites hello à 50 shades of grey

La couleur, c’est le nerf de la guerre du dark mode. Le mieux c’est évidemment d’avoir un designer, car designer un dark mode est un vrai métier. Mais on peut limiter la casse avec quelques bonnes pratiques :

2.1 Pas de noirs et blancs purs :

Faire un dark mode ce n’est pas juste mettre un background-color en #000000. Ce qu’on cherche à éviter, c’est le contraste pur : donc pas de fond noir, ni de textes/images en blanc.

Résultat : on privilègie des gris très foncés pour les fonds et des gris clairs pour les textes. Google recommande par exemple #121212 en fond, Apple #1c1c1c.

Pour les textes, on différencie des types de gris clairs qui permettent d’avoir autant de types de texte différents. Par exemple, sur mon site, je suis partie sur 3 types de textes (ceux que j'utilise le plus) et j’ai appliqué à la couleur de base une opacité différente pour chacun de ces types (celle proposée par Google me semblait la plus confortable):

  1. disabled : 38%,
  2. normal : 60%,
  3. strong : 87%.

Besoin d'un coup de pouce ? Voici la "Recommandation outils" pour cette section : les alternatives aux noirs et blancs purs.

2.2 Pas de couleurs saturées :

Les couleurs saturées sur le noir, ça fait vite néon, et ça rend la lisibilité nulle. Ça implique de changer les couleurs. Ça veut dire que oui, il faut un thème différent pour le dark mode.

Résultat : on fait l’état des lieux de ses couleurs et de leur usage sur le site, on génère un nouveau thème avec les mêmes couleurs pour les mêmes usages, mais avec moins de saturation et on vérifie le tout sur des contrast checker.

Besoin d'un coup de pouce pour ça ? Voici le dark theme generator.

Comparaison des couleurs sur materialUI

Le guide material design de Google illustre la comaparaison de saturation des couleurs sur différents fonds

2.3 Pas d’ombres :

Sur fond blanc, une ombre c’est parfait. Sur fond noir, c’est quoi ? L’ombre dans le noir, c’est contre-intuitif, on oublie. Eventuellement, on peut ajouter un outter-glow ou une stroke, mais l’idéal est de repenser sa charte de couleurs et de réfléchir en tenant compte du point 4 ci-dessous (car l’usage des ombres est en fait souvent un moyen de signifier des couches différentes).

Besoin d'un coup de pouce pour donner du glow à tes textes ? J'ai l'outil pour ça.

2.4 On repense le layout en “couches” :

On peut envisager des différences de background pour mettre en avant certains items, en jouant sur les nuances de gris : dans ce cas, la règle c’est “higher elevation, lighter surface”. En effet, en light mode, c’est souvent une ombre ou une ligne qui signifie la différence de layout. En dark mode, sans ombre, il faut jouer sur le fond.

exemple des cartes des articles du blog en light mode: les couches sont signifiées par des ombres

Les articles du blog en light mode : les couches sont signifiées par des ombres.


exemple des cartes des articles du blog en darkmode: les couches sont signifiées par des nuances de gris.

Les articles du blog en dark mode : les couches sont signifiées par des nuances de gris.

En conclusion : ce que ces points disent en terme de design, c'est qu'il faut thémer son dark mode. Faire un dark mode ce n’est en effet pas uniquement “inverser” les couleurs de son site. Impossible de mettre des filter: invert(1) partout. Il faut redéfinir les layers, choisir les couleurs, rendre cohérent leur usage sur le site/entre les composants. L’idéal est donc de travailler son dark mode comme un thème en soi, en définissant les propriétés qui se partagent entre les composants, comment réagit chaque composant, etc.

Techniquement, définir un vrai thème en amont facilite aussi son implémentation : selon les outils et la stack technique utilisés pour le front, il sera du coup plus simple d’implémenter le dark mode et surtout il sera davantage maintenable.

Chapitre bonus. Deux points de vigilance : l’accessibilité et les images

  1. l’accessibilité : On l’a déjà évoqué en parlant de vérifier ses contrastes de couleurs et ça parait évident (mais ça vaut toujours le coup de le redire) : ce n’est pas parce qu’on passe en dark mode qu’on oublie l’accessibilité. Donc tous les paramètres habituels s’appliquent évidemment, mais on prend garde à trois éléments en particulier.

    • attention aux contrastes ;
    • attention aux focus qui peuvent sauter ;
    • attention aux targets sizes.

    Pour rappel, une multitude d'outils existent pour checker vos contrastes de couleurs et vos focus.

  2. (Re)considérer l’utilisation, le format et le choix des images : quand on a dit qu’il fallait prendre garde aux couleurs, cela s’applique aussi aux images et là, on sent monter une petite angoisse, non ?

    En effet, les couleurs des images ne rendent pas du tout de la même manière sur fond blanc que sur fond noir : comme les couleurs de textes ou d’éléments, les couleurs utilisées en light mode vont avoir un effet néon dans un thème sombre, à cause d’une saturation trop importante. Pour commencer, on vérifie donc que nos images n'ont pas de fonds blancs (qui ne se verrait pas sur un thème light, mais qui va exploser les yeux de vos users en dark mode).

    Après, on a deux options : soit on garde les mêmes images pour les deux thèmes en vérifiant les fonds et en diminuant un peu leur saturation, soit on change complètement les images.

    Est-ce qu’il faut alors systématiquement prévoir des images différentes pour le dark mode ?

    La réponse idéale est oui : pour cela, la première règle de base est d'avoir ses images en SVG. Puis, plusieurs options dans le CSS, suivant comment vous gérez votre CSS (avec une librairie, en css vanilla, etc.), l’idée de base restant la même : switcher l’url de la source de votre image selon le thème choisi par l’utilisateur.

    C’est la meilleure option, mais la plus fastidieuse : si on n’a pas le temps, je te donne le cheat code : appliquer un simple filtre greyscale sur ses images et diminuer le contraste avec : filter: brightness(.8) contrast(1.2). Attention, on vérifie quand même qu’aucune image n’a de fond blanc, c’est non négociable. Et on bannit l’utilisation d’un filter: invert(1), c’est beaucoup trop sale.

    On pourrait essayer de faire les malins et de se dire que l’utilisation d’images uniquement en noir et blanc règle le problème. Ça a été mon cas sur ce site : sans y avoir pensé en amont, j’ai choisi d’utiliser des images en noir et blanc pour illustrer les articles de mon blog. En thémant le dark mode, j’ai naivement pensé que ca serait un souci en moins. Pas du tout. En contraste inversé, le blanc sur noir et bien plus difficile à supporter que le noir sur blanc. En fait, la même logique que celle évoquée ci-dessus pour les textes s’applique : là ou en light mode on utilise du noir sur blanc, en dark mode, il faut réfléchir en nuances de gris.

Chapitre 3. La technique : maitriser le prefers-color-scheme entre deux cookies

3.1 On s'attaque au CSS

Tout d’abord, sachez simplement qu’il y a quelques bases utiles à connaître pour se lancer dans les dark themes :

La media feature prefers-color-scheme : on rappelle qu’une media feature décrit des caractéristiques spécifiques à un appareil, à une taille d’écran, à un environnement, etc. et permet de leur appliquer un style spécifique avec la syntaxe :

@media (max-width: 1250px) {
    color: red;
}

On peut donc changer le style de classes spécifiques selon la préférence du thème. La media feature prefers-color-scheme check si l’user a une préférence enregistrée via son OS ou un paramètre de l’user agent. Si on applique ça de manière pure en css, ça donnerait quelque chose comme :

@media (prefers-color-scheme: dark) {
    body {
        background-color: #121212;
        color: #f1f1f1;
    }
}

Si vous avez fait du Sass ou Scss ou du preprocessor css vous avez surement déjà entendu parler des mixins. Les mixins sont très utiles pour abstraire du styles d’une classe que l’on veut réutiliser ailleurs (par exemple du texte caché). Vous me direz on peut juste en faire une classe et l’appliquer partout, mais bon ça peut vite devenir un enchainement de classes dans le code.

Donc on définit un mixin qu’on va ensuite appliquer dans les classes que l’on souhaite. On a par exemple une classe .text dans notre css qui va s’appliquer à tous les textes :

.text {
  color: black;
  text-align: center;
}

On peut le transformer en mixin et appliquer ce style dans les autres classes :

Vous me direz, "Mais Audrey, pourquoi tu nous parles d'un truc spécifique à Sass ?". Parce que d'une part il y a des bibliothèques de composants React qui se basent là-dessus, donc c'est toujours utiles de savoir que ça existe et, d'autre part, car la proposition d'ajouter des mixins dans le CSS a été acceptée en 2024 et est en cours d'implémentation !

C’est souvent sur ces concepts de CSS que vont s’appuyer les librairies pour fournir des solutions de dark mode "clé en main” comme la librairie next-themes pour le prefers-color-scheme, par exemple, librairie sur laquelle s’appuie Chakra UI, ShadcnUI, ou Mantine avec les mixins.

3.2 Ce que le SSR fait au darkmode

Le deuxième point technique qui me semble le plus intéressant est celui évoqué au début de l’article : est-ce qu’on stocke la préférence utilisateur et si oui comment ? On ne peut pas échapper à ce que j’appellerais un “stockage minimal” à savoir : dire quelque part au navigateur que le thème est light ou dark. Mais on a différentes options, selon les besoins et implémentations techniques: base de données, local storage, cookies ou tout simplement dans un context par exemple, en React.

La seule façon d’implémenter un mode qui soit persistant entre les appareils et dans le temps, malgré les caches, rafraichissements de pages et suppression des cookies, c’est de stocker cette préférence dans une base et de la récupérer en temps voulu pour la mettre dans un cookie.

L'option stockage en base est aussi celle qui va le mieux avec l'option citée plus haut de considérer la préférence de thème comme un paramètre utilisateur, géré dans les settings par exemple.

L’option DB est la plus extrême, d’autres options de stockage existent, sachant notamment que la préférence de thème peut dépendre d’un appareil à un autre : l’user ne voudra peut-être pas avoir du dark mode sur tous ses appareils. Dans ce cas, on peut par exemple s’appuyer sur le local storage.

Le + de Dre Drey

Avec son usage uniquement côté client et son absence d’expiration, le local storage semble une bonne option intermédiaire de stockage de la préférence utilisateur: le thème choisi restera persistant sur le même navigateur, mais peut être différent selon les appareils. Par ailleurs, aucune information personnelle n’est envoyée au serveur, ce qui de manière générale est plutôt une bonne chose.

Le local storage a malheureusement un problème, et pas des moindres : si vous avez bien suivi, tout se passe côté client. Avec une simple app react, ou du JS, cela ne pose pas de problème. Mais si comme moi vous êtes sur Nextjs par exemple ou que vous utilisez du SSR : votre page sera d’abord chargée côté serveur avec les informations que le serveur possède. Pas le local storage, donc. Et le navigateur mettra à jour la page avec les informations qu’il aura ensuite. Pour certaines features, ça ne pose pas de problèms, mais pour l’implémentation d’un dark mode, cela peut causer ce qu’on appelle “un flash” ou du flickering : l’utilisateur verra d’abord la page dans un thème (celui que le serveur connait, souvent par défaut le light), puis sa préférence. Et si c’est le thème sombre, ca crée un flash.

La solution ? Les cookies. Eux peuvent être construits et chargés du côté du serveur. Cela veut dire que quand la page est chargée côté serveur pour les frameworks SSR, elle peut directement accéder à la préférence utilisateur stockée dans le cookie. Attention : pour Nextjs par exemple, la doc est très claire sur le sujet et se fend d'un beau paragraphe sur "Comment fonctionnent les cookies dans un Server Component", parce que c'est trés important : c'est important de comprendre que, bien que fonctionnant dans un server component, un cookie est fondamentalement un mécanisme de stockage du client. On peut lire un cookie dans un server component, parce qu'on accède au cookie que le navigateur envoie au serveur dans la requête HTTP. MAIS, par exemple, set un cookie ne peut être fait dans un server component, car un cookie reste stocké dans le navigateur, non le serveur.

Le serveur ne fait qu'envoyer une instruction dans son header, pour dire au navigateur de set un cookie. Pour être claire :

  1. Votre user arrive sur votre site pour la 1ère fois : sa préférence de thème est set dans un cookie par le navigateur.
  2. Il part et revient : le cookie est toujours là, associé au nom de domaine de votre site, et il est envoyé dans la requête de la page au serveur.
  3. Le serveur peut lire le cookie et appliquer le thème correspondant avant de renvoyer la page au navigateur.
  4. Le navigateur reçoit la page avec le thème correspondant au cookie et l’affiche dans le render initial.
  5. Le user change de thème : le cookie est mis à jour avec la nouvelle valeur. etc.

J'en profite pour celles et ceux qui n'auraient pas vu passer l'info pour rappeler que la synchronicité de la fonction cookie() de nextjs change entre la version 14 et la 15 : avant la version 15, cette fonction était asynchrone et elle devient synchrone dans la version 15.

Le + de Dre Drey

De mon côté, tu remarqueras peut-être que mon dark mode n'est pas non plus parfait (je ne suis pas très satisfaite de l'usage des couleurs et des hovers par exemple) ! Et ce n'est pas grave, j'ai appris plein de choses en esayant de l'implémenter et je t'encourage vraiment à le tenter, car ça permet de mettre plein de choses au point et de revoir plein de basiques.

Des ressources supplémentaires

Les guides design utiles :

Des articles de fond pour comprendre les tendances du dark mode :

  • Dash, P., & Hu, Y. C. (2021, June). How much battery does dark mode save? An accurate OLED display power profiler for modern smartphones. In Proceedings of the 19th Annual International Conference on Mobile Systems, Applications, and Services (pp. 323-335).
  • Eisfeld, H., & Kristallovich, F. (2020). The rise of dark mode : A qualitative study of an emerging user interface design trend.
  • Erickson, A., Kim, K., Bruder, G., & Welch, G. F. (2020, March). Effects of dark mode graphics on visual acuity and fatigue with virtual reality head-mounted displays. In 2020 IEEE Conference on virtual reality and 3D user interfaces (VR) (pp. 434-442). IEEE.

D'autres dev qui se sont penchés sur la question, avec des stacks différentes et des problèmes variés:

  • Brandur, mai 2024, Notes on implementing dark mode est un article qui pousse l'idée du tri-state (l'utilisateur n'a rien choisi OU il a choisi le light OU il a choisi le dark) qui n'est pas inintéressant.
  • K. Jin, décembre 204, revient sur la différence Local Storage/Cookies
  • Josh Comeau, mars 2025, a récemment écrit un article qui m'a beaucoup rassurée (dans lequel il te dit que le dark mode a été plus compliqué à implémenter que son layer GraphQL).
  • Une autre voie que celle du cookie et du localstorage ? C'est ce que propose Tushar Shahi dans son article de 2022.