9.3 — Piloter le mode en JavaScript
⏱️ TL;DR — Un sélecteur de thème = changer
data-bs-themesur<html>en JS, le persister (localStorage) et respecter la préférence système (prefers-color-scheme). Pour éviter le flash au chargement (SSR), appliquez le thème via un petit script inline dans le<head>, avant le rendu.
🎯 Objectifs
- Basculer le thème en JS.
- Persister le choix et suivre la préférence système.
- Éviter le flash clair→sombre.
Basculer et persister
<button id="toggle" class="btn btn-outline-secondary">🌓 Thème</button>const html = document.documentElement
function setTheme(theme) {
html.setAttribute('data-bs-theme', theme)
localStorage.setItem('theme', theme)
}
document.getElementById('toggle').addEventListener('click', () => {
const current = html.getAttribute('data-bs-theme')
setTheme(current === 'dark' ? 'light' : 'dark')
})Choix initial : stocké > système > clair
function preferredTheme() {
const stored = localStorage.getItem('theme')
if (stored) return stored
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
setTheme(preferredTheme())Suivre la préférence système en direct
window.matchMedia('(prefers-color-scheme: dark)')
.addEventListener('change', e => {
if (!localStorage.getItem('theme')) { // seulement si l'utilisateur n'a pas choisi
setTheme(e.matches ? 'dark' : 'light')
}
})Éviter le flash (FOUC de thème)
Le problème : la page se peint en clair, puis le JS passe en sombre → un flash. Solution : un script bloquant et inline tout en haut du <head>, exécuté avant le premier rendu :
<head>
<script>
(function () {
var t = localStorage.getItem('theme')
|| (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
document.documentElement.setAttribute('data-bs-theme', t)
})()
</script>
<!-- puis le CSS -->
</head>⚠️ Piège — Mettre ce script en bas de page ou en
defer: trop tard, le flash a lieu. Il doit être synchrone, dans le<head>, avant le contenu.
💡 Pour un dev React / Next.js — Ne réinventez pas la roue :
next-themesgère toute cette logique (persistance, système, anti-flash via un script injecté) et fonctionne avecdata-bs-themeen passantattribute="data-bs-theme". C’est la solution recommandée en App Router. Voir Partie 11.
✏️ Exercices
- Écrire la fonction qui choisit le thème initial (stocké, sinon système).
✅ Solution
Voir preferredTheme() ci-dessus : localStorage.getItem('theme') puis matchMedia('(prefers-color-scheme: dark)').
- Pourquoi le script anti-flash doit-il être inline et synchrone dans le
<head>?
✅ Solution
Pour appliquer le thème avant le premier rendu ; sinon la page s’affiche en clair puis « saute » en sombre.
🧠 Quiz de révision
1. Comment changer le thème en JS ?
document.documentElement.setAttribute('data-bs-theme', …).
2. Comment persister le choix ?
localStorage.
3. Quelle media query donne la préférence système ?
prefers-color-scheme: dark.
4. Comment éviter le flash de thème ?
Un script inline synchrone dans le <head>, avant le rendu.
5. Quelle lib gère tout ça en Next.js ?
next-themes (avec attribute="data-bs-theme").
🎉 Fin de la Partie 9 ! Partie suivante : Partie 10 — Le JavaScript de Bootstrap