Aller au contenu
← 1001 Widgets
N° 015 · ●○○

Bouton clair / sombre

Un bouton clair, sombre ou système, qui respecte le réglage de l'ordinateur tant qu'on ne choisit rien. Le bon exercice pour comprendre les préférences de l'utilisateur.

CSSJS
Ce qu'on apprend
  • Lire prefers-color-scheme avec matchMedia
  • Retenir un choix dans localStorage
  • Changer de thème avec des variables CSS
Pourquoi ce langage

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.

Comment ça marche
  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();
Pièges

PIÈGE 1 : Le flash blanc au chargement

Piège

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

Piège

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

Piège

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.

Commentaires
0/1000

Chargement des commentaires…