Retour aux articles

Créer un « dark mode » complet en CSS et Javascript

Il existe plusieurs possibilités pour implémenter un « dark mode » sur un site internet. w3school propose dans ce tutoriel d’activer ou de désactiver une classe CSS dark-mode sur le body. Cette méthode devient vide contraignante lorsque l’on travaille avec un design-system complexe. Le tutoriel n’explique pas non plus comment enregistrer les réglages de utilisateurs. Nous allons voir ici comment faire ça en utilisant les variables CSS (custom properties) et comment enregistrer les préférences de l’utilisateur à l’aide du localStorage du navigateur.

Mettre en place les variables CSS

Dans votre feuille de style principale, définissez des variables CSS (custom properties) de votre design-system.

:root {
  --background: #eee;
  --background_solid: #fff;
  --font_color: #292929;
  --title_color: #000;
  --btn_background: #0038FF;
  --btn_color: #fff;
  --svg_sky:#c6d3ff;
  --svg_sun:#fff676;
  --svg_beam_opacity:0;
}

la pseudo classe :root représente la balise <html>. Cela signifie que ces variables CSS sont définies au niveau du document html et sont donc disponibles et utilisables dans toutes les classes des éléments du document. Nous allons ensuite définir leur valeurs lorsque le document est en « dark mode » :

:root[data-theme="dark"] {
  --background: #091022;
  --background_solid: #111b33;
  --font_color: #d3ddff;
  --title_color: #fff;
  --btn_background: #c6d3ff;
  --btn_color: #000;
  --svg_sky:#000;
  --svg_sun:#fff;
  --svg_beam_opacity:0.1;
}

L’application du « dark mode » consistera donc simplement à ajouter l’attribut de donnée data-theme="dark" à l’élément racine <html>. En faisant cela, la valeur des variables est modifiée instantanément.

Activer le dark mode en javascript

Mettre en place un bouton pour changer le thème :

<button id="darkMode">Toggle Dark Mode</button>

Lorsque l’on clique sur ce bouton, il faut switcher entre le mode normal et le dark mode en modifiant dynamiquement la valeur de l’attribut data-theme sur le document ( la balise <html> ):

document.getElementById('darkMode').onclick = () => {
  var currentTheme = document.documentElement.getAttribute("data-theme");
  var newTheme = currentTheme === "dark" ? "light" : "dark";
  document.documentElement.setAttribute("data-theme", newTheme);
};

Enregistrer le choix de l’utilisateur

A chaque appui sur ce bouton, il faut également enregistrer la valeur de l’attribut de donnée data-theme qui viens d’être appliquée au document pour pouvoir la ré-appliquer lorsque l’utilisateur change de page ou quitte le site et reviens plus-tard. Pour ce genre de chose, on utilise le localstorage de son navigateur. Le code précédent devient :

document.getElementById('darkMode').onclick = () => {   
  var currentTheme = document.documentElement.getAttribute("data-theme");     
  var newTheme = currentTheme === "dark" ? "light" : "dark";                
  document.documentElement.setAttribute("data-theme", newTheme);       
  localStorage.setItem( "theme" , newTheme ); 
};

Il reste maintenant à récupérer cette valeur au chargement de la page.

document.addEventListener("DOMContentLoaded", () => {
  let theme = localStorage.getItem("theme") || false;
  if (theme) document.documentElement.setAttribute("data-theme", theme);
});

Et voila ! le thème est enregistré dans le navigateur et ré-appliqué automatiquement lorsque l’utilisateur change de page ou reviens sur l’application.

Détecter les préférences système

Sur certains sites, le « dark mode » s’active automatiquement lorsqu’il détecte les préférences systèmes. Pour mettre cela en place, il suffit de détecter une media query bien précise qui donne cette information. On utilise la méthode window.matchMedia pour détecter cette query. Le code précédent devient alors :

document.addEventListener("DOMContentLoaded", () => {
  let theme = localStorage.getItem("theme") || false;
  if( !theme ) theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : false ;
  if (theme) document.documentElement.setAttribute("data-theme", theme);
});

Si l’utilisateur n’a jamais paramétré le thème sur l’application, il n’est alors par récupéré dans le localstorage et on vérifie alors la présence d’une media query prefers-color-scheme: dark.

Problème de flash lors du chargement d’une page

Il peut arriver que lorsque l’utilisateur change de page, le body soit d’abord de la couleur par défaut définie dans :root puis le « dark mode » est appliqué par le javascript. Cela peut créer un effet « flash » non désirable. Pour remédier à ça, on peut ajouter quelques lignes CSS dans une balise <style> dans le <head> du document, comme ceci :

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        body {
            visibility: hidden;
            opacity: 0;
        }
    </style>
</head>

Au chargement du document, le body n’est pas visible. Il sera rendu visible par le javascript uniquement après l’application du theme. Notre fonction d’initialisation devient alors :

document.addEventListener("DOMContentLoaded", () => {
  let theme = localStorage.getItem("theme") || false;
  if( !theme ) theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : false ;
  if (theme) document.documentElement.setAttribute("data-theme", theme);
document.body.style.visibility = 'visible';
  document.body.style.opacity = 1;
});

Et voila ! Un dark mode complet et moderne basé sur les variables CSS.