Skip to content
← 1001 Widgets
N° 015 · ●○○

Light / dark toggle

A light, dark or system toggle that follows the computer's setting until you choose. The right exercise to understand user preferences.

CSSJS
What you'll learn
  • Read prefers-color-scheme with matchMedia
  • Remember a choice in localStorage
  • Switch themes with CSS variables
Why this language

Pourquoi un bouton de thème ? Parce que beaucoup de gens règlent leur téléphone en sombre, et qu'un site tout blanc à minuit les éblouit.

La bonne règle : suivre le système par défaut, et laisser l'utilisateur forcer clair ou sombre s'il le veut. Son choix est retenu pour la prochaine visite.

Pourquoi en JavaScript simple ? Le CSS fait presque tout avec des variables. Le JavaScript ne fait que poser un attribut sur la page et retenir le choix.

How it works
  1. LES COULEURS EN VARIABLES

    Un seul jeu de règles, deux jeux de couleurs.

    :root { --fond: #ede8df; --texte: #09090e; }
    :root[data-theme="sombre"] { --fond: #09090e; --texte: #ede8df; }
    body { background: var(--fond); color: var(--texte); }
  2. LES TROIS BOUTONS

    <div role="group" aria-label="Thème">
      <button data-t="clair">Clair</button>
      <button data-t="sombre">Sombre</button>
      <button data-t="systeme">Système</button>
    </div>
  3. LE RÉGLAGE DU SYSTÈME

    matchMedia lit la préférence, et prévient quand elle change.

    const systeme = matchMedia('(prefers-color-scheme: dark)');
  4. APPLIQUER

    Le choix enregistré gagne. Sans choix, on suit le système.

    function appliquer() {
      const choix = localStorage.getItem('theme') || 'systeme';
      const theme = choix === 'systeme' ? (systeme.matches ? 'sombre' : 'clair') : choix;
      document.documentElement.dataset.theme = theme;
      document.querySelectorAll('[data-t]').forEach(b =>
        b.setAttribute('aria-pressed', String(b.dataset.t === choix)));
    }
  5. LES ÉCOUTEURS

    document.querySelectorAll('[data-t]').forEach(b =>
      b.addEventListener('click', () => {
        localStorage.setItem('theme', b.dataset.t);
        appliquer();
      }));
    systeme.addEventListener('change', appliquer);
    appliquer();
Pitfalls

PIÈGE 1 : Le flash blanc au chargement

Pitfall

Mauvais : appliquer le thème dans un script en bas de page. La page s'affiche en clair, puis bascule.

Fix

Correct : un petit script dans le <head>, avant le CSS, qui pose data-theme tout de suite.

PIÈGE 2 : Ignorer le système

Pitfall

Mauvais : démarrer toujours en clair et obliger chacun à cliquer.

Fix

Correct : prefers-color-scheme par défaut, et le bouton seulement pour changer d'avis.

PIÈGE 3 : Deux feuilles de style

Pitfall

Mauvais : charger un fichier CSS différent pour chaque thème. Chaque bascule recharge et clignote.

Fix

Correct : des variables CSS, et un seul attribut qui change.

PERSONNALISER
  • Ajoutez color-scheme: light dark dans :root pour que les barres de défilement et les champs suivent aussi.
  • Un troisième thème, par exemple contraste élevé : un bloc de variables de plus suffit.
  • Une transition douce sur background et color, coupée avec prefers-reduced-motion.
OPTIMISATION
  • La meta theme-color peut changer avec le thème pour colorer la barre du navigateur sur mobile.
  • Pensez aux images : un logo sombre disparaît sur un fond sombre, prévoyez une version claire avec <picture> et prefers-color-scheme.
VERS REACT

Le choix vit dans un contexte (createContext) pour que toute l'application y ait accès.

Un useEffect écoute matchMedia et nettoie son écouteur au démontage.

Le script anti-flash reste en dehors de React, dans le <head> du document.

Comments
0/1000

Loading comments…

Audience measurement

With your consent, this site measures its traffic with Google Analytics and the Meta pixel, via Google Tag Manager. Nothing is loaded until you choose, and you can change your mind at any time with the "Cookies" link at the bottom of the page.