Image de couverture - Comment créer un dark mode en CSS et JS ?

Comment créer un dark mode en CSS et JS ?

De plus en plus présent sur les interfaces, le dark mode a gagné en popularité au fil des années. Mais comment créer un dark mode en CSS et JS ?

Icône de calendrier
Icône de chronomètre 8 min

Si les loups-garous se transforment les nuits de pleine lune, le dev, lui, se transforme en chauve-souris le jour où il découvre le dark mode. Dans cet article, nous allons voir comment créer un système de dark mode en CSS et JS.

Pourquoi créer un dark mode ?

Le mode sombre (communément traduit par « Dark Mode ») consiste à remplacer les couleurs claires d’une interface par des tons plus foncés, réduisant ainsi la luminosité de l’écran.

Ce mode est de plus en plus populaire dans les applications et sites web car il offre une expérience visuelle différente et adaptée aux préférences des utilisateurs.

Citons par exemple quelques avantages du dark mode :

  • 😵‍💫 Réduction de la fatigue visuelle : Moins agressif pour les yeux, le dark mode est idéal pour l’usage d’écrans dans des environnements peu éclairés.
  • 🔋 Économie d’énergie : Sur les écrans OLED, les pixels sombres consomment moins d’énergie.
  • 🎯 Focalisation : En réduisant la luminosité générale, le mode sombre peut réduire la distraction visuelle et est perçu par certains comme vecteur de concentration.
  • 🤓 Culture tech : Le mode sombre est souvent associé au milieu de la tech car il contribue à offrir de la profondeur et des contrastes élevés, bien souvent recherchés dans ce secteur. La plupart des sites web tech, IDE et terminaux de développement sont aujourd’hui par défaut en mode sombre.

Comment créer un dark mode ?

Implémenter un dark mode se résume à ajouter sur la balise <html> ou <body> un attribut spécifique, consistant à conditionner le CSS à sa présence.

Cet attribut est généralement représenté par une classe HTML ou un data-attribute.

Avec une classe .dark

De nombreux sites conditionnent leur déclinaison en dark mode à la présence d’une classe (souvent nommée .dark) sur la balise de premier niveau <html> ou <body>.

copié !
<html lang="fr">
  <!-- 🌞 Classe absente : le site est en version claire -->
</html>
copié !
<html lang="fr" class="dark">
  <!-- 🌚 Classe présente : le site est en version sombre -->
</html>

Le CSS sera ainsi conditionné de la manière suivante :

copié !
html {
	background-color: #ececec;
	color: #212121;
}

html.dark {
	background-color: #212121;
	color: #ececec;
}

Avec un attribut data-*

Alternativement, sans passer par une classe dans le HTML, il est possible d’implémenter un système de dark mode à travers des data-attribute.

Nous appellerons le nôtre… 🥁🥁🥁 data-theme.

copié !
<html lang="fr" data-theme="light">
  <!-- 🌞 Le site est en version claire -->
</html>
copié !
<html lang="fr" data-theme="dark">
  <!-- 🌚 Le site est en version sombre -->
</html>

Le CSS sera ainsi conditionné de la manière suivante :

copié !
[data-theme="light"] {
	background-color: #ececec;
	color: #212121;
}

[data-theme="dark"] {
	background-color: #212121;
	color: #ececec;
}

Je préfère personnellement cette option, c’est donc ainsi que nous implémenterons notre dark mode.

Tutoriel : créer un dark mode

Côté HTML

Dans notre fichier HTML, il nous faudra d’abord lier le style CSS du dark mode ainsi que sa logique JS.

Chargement des assets

index.html
copié !
<!DOCTYPE html>
<html lang="fr">
  <head>
    ...
    <script src="js/dark-mode.js"></script>
    <link rel="stylesheet" href="css/dark-mode.css" />
  </head>
  <body>
    ...
  </body>
</html>
Pourquoi avoir lié le JS dans le <head> et non avant la fermeture du <body> ?

En liant le JS dans le <head>, on applique directement le thème dès le chargement de la page.

Cela permet d’éviter le problème classique où, si le mode sombre est la préférence de l’utilisateur et le JS est chargé après le DOM, la page s’affiche d’abord en mode clair avant de passer en sombre.

Le principal objectif est ainsi d’éviter le flash de couleur apparaissant au chargement de la page.

Le script est léger, donc il n’y a pas de problème de performance à cela.

Création du toggler

Le toggler est l’élément de l’interface qui permettra de basculer du thème clair au thème sombre et inversement. Un toggler est aussi souvent désigné par le terme « switch ».

Il serait tout à fait possible d’identifier ces éléments avec une simple classe HTML, mais nous resterons dans notre logique d’utilisation d’un attribut data*, et ajouterons donc un attribut data-theme-toggler, sans valeur.

index.html
copié !
<button data-theme-toggler>Changer de mode</button>

Côté CSS

En ce qui concerne le fichier CSS, la seule obligation est de contenir les sélecteurs auxquels vont être associés les thèmes clair et sombre.

Considérons pour l’exemple le fichier CSS minimaliste suivant :

css/dark-mode.css
copié !
/* 🌞 Style clair */
[data-theme="light"] {
	--bg-color: #ececec;
	--text-color: #212121;
}

/* 🌚 Style sombre */
[data-theme="dark"] {
	--bg-color: #212121;
	--text-color: #ececec;
}

html {
	background-color: var(--bg-color);
	color: var(--text-color);
}

Il est en ce sens recommandé d’éviter les couleurs blanc et surtout noir, mais bien des teintes proches pour adoucir le contraste.

Par exemple :

  • #ececec pour le clair
  • #212121 pour le sombre

Ces teintes suffisent largement à obtenir un contrast ratio suffisant.

Pourquoi utiliser des variables CSS ?

Travailler avec des variables CSS confère une approche encore plus propre, en ne faisant varier que le contenu des variables au lieu de redéfinir les propriétés CSS dans deux sélecteurs distincts.

Côté JS

C’est dans le code JavaScript que les choses sérieuses commencent. 🤗

Nous allons pour notre système de dark mode procéder en 2 étapes :

  1. Implémenter la mécanique de changement de thème
  2. Enregistrer les préférences de l’utilisateur

L’ensemble de notre script sera contenu dans une IIFE afin de ne pas polluer la portée globale de l’application (portée globale, conflits de noms…).

js/dark-mode.js
copié !
(function () {
  
})();

Créer le système de switch du thème

1. Sélections sur le DOM

Commençons par sélectionner le nœud racine de notre page HTML, soit la balise <html>.

js/dark-mode.js
copié !
(function () {
  const root = document.documentElement;
})();
2. Ecouteurs d’évènements

Ici, l’évènement DOMContentLoaded nous permet de nous assurer que le DOM est chargé, ce qui est important dans la mesure où notre script est chargé dans le <head> (pour éviter le risque de « flash lumineux » évoqué précédemment).

Pour chaque toggler présent sur la page web, nous allons placer un écouteur d’évènement au click.

js/dark-mode.js
copié !
(function () {
  const root = document.documentElement;

  document.addEventListener("DOMContentLoaded", function () {
    const togglers = document.querySelectorAll("[data-theme-toggler]");
    togglers.forEach((toggler) => {
      toggler.addEventListener("click", toggleDarkMode);
    });
  });
})();
3. Permuter le thème

Il est désormais temps d’implémenter la fonction toggleDarkMode, déclenchée lors d’un clic sur un élément HTML de toggler.

js/dark-mode.js
copié !
(function () {
  const root = document.documentElement;

  document.addEventListener("DOMContentLoaded", function () {
    const togglers = document.querySelectorAll("[data-theme-toggler]");
    togglers.forEach((toggler) => {
      toggler.addEventListener("click", toggleDarkMode);
    });
  });

  function toggleDarkMode() {
    const currentTheme = root.getAttribute("data-theme");
    const newTheme = currentTheme === "dark" ? "light" : "dark";
    root.setAttribute("data-theme", newTheme);
  }
})();

Cette fonction se contentera de modifier l’état de l’attribut data-theme :

  • S’il vaut light, il devient dark
  • S’il vaut dark, il devient light

Enregistrer les préférences de l’utilisateur

1. Sauvegarde dans le localStorage

Pour améliorer l’expérience utilisateur, il est intéressant de sauvegarder le choix de l’utilisateur dans le localStorage du navigateur, afin qu’il persiste entre les visites.

On ajoute donc un appel à la méthode setItem() en fin de fonction toggleDarkMode().

js/dark-mode.js
copié !
function toggleDarkMode() {
  const currentTheme = root.getAttribute("data-theme");
  const newTheme = currentTheme === "dark" ? "light" : "dark";
  root.setAttribute("data-theme", newTheme);
  localStorage.setItem("theme", newTheme);
}
2. Chargement du thème adéquat

Pour déterminer le thème à appliquer dès le chargement de la page, il est nécessaire de combiner deux types de préférences utilisateur :

  1. Les préférences spécifiques au site
  2. Les préférences système

Implémentons le chargement de ces préférences au sein d’une fonction d’initialisation init(), par souci de clarté.

Préférences spécifique au site

Les préférences spécifiques au site (enregistrées dans le localStorage) permettent de conserver le choix effectué précédemment par l’utilisateur sur votre site.

Pour cela, on récupère dans storedPreference la valeur du thème stockée dans le localStorage.

js/dark-mode.js
copié !
(function () {
// ...

 function init() {
    const storedPreference = localStorage.getItem("theme") || "light";
    root.setAttribute("data-theme", storedPreference);
  }

  init();
  
  document.addEventListener("DOMContentLoaded", function () {
    // ...
  });

  function toggleDarkMode() {
    // ...
  }
})();

On ajoute à l’attribut data-theme la valeur stockée dans le localStorage. Si aucun thème n’est défini, on le définit par défaut à light (avec l’opérateur ||).

La fonction init() est donc déclenchée dans la foulée.

Préférences système

Les préférences système (prefers-color-scheme) indiquent si l’utilisateur a configuré son système d’exploitation pour préférer un thème clair ou sombre.

Dans le cas où le visiteur n’a pas de préférence spécifique au site (autrement dit, s’il n’a jamais modifié le thème), alors il peut être intéressant de vérifier s’il a une préférence définie au niveau de son système d’exploitation, avant de se rabattre par défaut sur du blanc.

js/dark-mode.js
copié !
 function init() {
    const storedPreference = localStorage.getItem("theme");
    const systemPrefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
    const theme = storedPreference || (systemPrefersDark ? "dark" : "light");
    root.setAttribute("data-theme", theme);
  }

La méthode window.matchMedia("(prefers-color-scheme: dark)") retourne un objet MediaQueryList, et sa propriété .matches retourne un booléen (true ou false) en fonction des préférences système de l’OS de l’utilisateur.

  • Si systemPrefersDark vaut true : alors l’utilisateur préfère le dark mode
  • Si systemPrefersDark vaut false : alors l’utilisateur préfère le light mode

Ensuite on analyse d’abord la préférence de l’utilisateur :

  1. S’il en a une, la valeur de storedPreference est conservée. Et on s’arrête là.
  2. S’il n’en a pas, il va chercher si une préférence système systemPrefersDark est définie.
  • Si oui : il la considère
  • Si non : ca sera light par défaut

Il est important de donner la priorité à la préférence spécifique au site (échelle micro) sur celle du système (échelle macro) pour initialiser le thème de manière cohérente.

Code JavaScript complet

Voici le script complet contenant l’implémentation de notre dark mode en JavaScript :

js/dark-mode.js
copié !
(function () {

  const root = document.documentElement;

  function toggleDarkMode() {
    const currentTheme = root.getAttribute("data-theme");
    const newTheme = currentTheme === "dark" ? "light" : "dark";
    root.setAttribute("data-theme", newTheme);
    localStorage.setItem("theme", newTheme);
  }

  function init() {
    const storedPreference = localStorage.getItem("theme");
    const systemPrefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
    const theme = storedPreference || (systemPrefersDark ? "dark" : "light");
    root.setAttribute("data-theme", theme);
  }

  init();

  document.addEventListener("DOMContentLoaded", function () {
    const togglers = document.querySelectorAll("[data-theme-toggler]");
    togglers.forEach((toggler) => {
      toggler.addEventListener("click", toggleDarkMode);
    });
  });

})();

Vous avez désormais toutes les clés pour implémenter un dark mode performant dans vos projets web. Simple à mettre en œuvre, il apporte confort visuel et modernité à vos interfaces.

Lire aussi