Chapitre 32 : Variables CSS pour la gestion dynamique des thèmes
Redéfinition des propriétés personnalisées (custom properties) selon le contexte pour créer des systèmes de thèmes évolutifs.
La Gestion Dynamique des Thèmes via Custom Properties
1. Quoi
La gestion dynamique des thèmes repose sur l'utilisation des Custom Properties (variables CSS). Contrairement aux variables de préprocesseurs comme Sass ou Less, qui sont compilées en valeurs statiques lors du build, les variables CSS sont résolues au runtime par le navigateur.
Cela signifie qu'une variable définie avec la syntaxe --ma-couleur: blue; peut être modifiée dynamiquement en changeant simplement la valeur de cette propriété sur un élément parent ou via JavaScript. Le navigateur met alors à jour instantanément tous les éléments enfants qui utilisent cette variable via la fonction var().
Le concept clé ici est la cascade. En redéfinissant une variable dans un sélecteur plus spécifique (comme une classe .dark-theme ou un attribut [data-theme="dark"]), on change la valeur de toutes les variables héritées pour l'ensemble du sous-arbre DOM.
2. Pourquoi
Dans un projet professionnel, coder les couleurs en dur ou multiplier les classes utilitaires (ex: .bg-white .text-black pour le jour et .bg-gray-900 .text-white pour la nuit) devient rapidement ingérable.
L'utilisation de variables pour le thémage offre plusieurs avantages majeurs :
- Maintenance centralisée : Pour changer la couleur primaire de tout le site, on modifie une seule ligne dans le
:root. - Performance : Le changement de thème ne nécessite pas de recharger la page ou de recalculer des milliers de classes CSS ; le navigateur se contente de recalculer les styles basés sur les nouvelles valeurs des variables.
- Flexibilité utilisateur : Permet d'implémenter facilement un "Dark Mode", des thèmes contrastés pour l'accessibilité, ou même des thèmes personnalisés par l'utilisateur (via un sélecteur de couleur).
- Découplage : Les composants ne savent pas quelle couleur ils utilisent (ex:
var(--color-surface)), ils savent seulement qu'ils utilisent une "surface". C'est le thème qui définit ce qu'est une "surface" à un instant T.
3. Comment
A. Syntaxe de base
On définit généralement les variables globales dans le pseudo-sélecteur :root (qui représente l'élément <html>), assurant ainsi que les variables sont disponibles partout.
:root {
/* Palette de base */
--primary-color: #3498db;
--bg-color: #ffffff;
--text-color: #333333;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
}
.btn-primary {
background-color: var(--primary-color);
}
B. Cas concret : Système de thèmes complet
Pour implémenter un système de thèmes, on utilise un attribut de données (data-attribute) sur l'élément <html> ou <body>. C'est la méthode la plus propre car elle est sémantique et facile à manipuler en JavaScript.
/* 1. Définition du thème clair (par défaut) */
:root {
--bg-main: #f4f7f6;
--bg-card: #ffffff;
--text-main: #2c3e50;
--text-muted: #7f8c8d;
--accent: #3498db;
--border-color: #dcdde1;
/* Transition fluide lors du changement de thème */
--theme-transition: 0.3s ease;
}
/* 2. Redéfinition pour le thème sombre */
[data-theme="dark"] {
--bg-main: #1a1a2e;
--bg-card: #16213e;
--text-main: #e9ecef;
--text-muted: #a2a8d3;
--accent: #4cc9f0;
--border-color: #0f3460;
}
/* 3. Application aux composants */
body {
background-color: var(--bg-main);
color: var(--text-main);
transition: background-color var(--theme-transition), color var(--theme-transition);
font-family: sans-serif;
}
.card {
background-color: var(--bg-card);
border: 1px solid var(--border-color);
color: var(--text-main);
padding: 1rem;
border-radius: 8px;
transition: background-color var(--theme-transition), border-color var(--theme-transition);
}
.card-title {
color: var(--accent);
}
.card-description {
color: var(--text-muted);
}
C. Surcharge locale (Scoped Variables)
L'un des aspects les plus puissants des variables CSS est la possibilité de les redéfinir pour un composant spécifique sans affecter le reste de la page.
/* Un composant "Sidebar" qui veut ses propres couleurs,
peu importe le thème global */
.sidebar {
--bg-card: #2c3e50; /* Surcharge locale de la variable */
--text-main: #ffffff;
background-color: var(--bg-card);
color: var(--text-main);
}
/* On peut même créer des variantes de composants */
.card-highlight {
--accent: #e74c3c; /* La carte aura un accent rouge, les autres resteront bleues */
}
D. Limitations
- Absence de fallback pour les navigateurs obsolètes : Les variables CSS ne fonctionnent pas sur Internet Explorer. Pour des projets supportant des navigateurs très anciens, un fallback statique est nécessaire.
- Calculs complexes : Bien que
calc()fonctionne avec les variables, on ne peut pas utiliser de variables CSS à l'intérieur d'une requête média (@media (max-width: var(--mobile-breakpoint))) car les media queries sont évaluées avant que le CSS ne soit appliqué au DOM. - Performance sur des milliers de variables : Redéfinir des centaines de variables sur un élément racine peut, dans des cas extrêmes, provoquer des recalculs de style coûteux si le DOM est immense.
4. Zone de Danger
❌ Erreur commune : Nommer les variables par leur valeur.
/* MAUVAIS : Si on change le thème, la variable s'appelle toujours "blue" mais vaut "white" */
:root { --main-blue: #0000ff; }
[data-theme="dark"] { --main-blue: #ffffff; }
✅ Bonne pratique : Nommer les variables par leur rôle (Sémantique).
/* BIEN : On décrit l'usage, pas la couleur */
:root { --color-primary: #0000ff; }
[data-theme="dark"] { --color-primary: #ffffff; }
Résolution des Variables CSS selon la Cascade
Questions clés
1. Quelle est la différence fondamentale entre une variable Sass ($var) et une variable CSS (--var) ?
Découvrir la réponse
La variable Sass est traitée lors de la compilation (build time) et disparaît du code final pour être remplacée par sa valeur. La variable CSS est traitée par le navigateur (runtime), ce qui permet de modifier sa valeur dynamiquement via JavaScript ou via la cascade CSS sans recompiler le code.
2. Pourquoi utiliser [data-theme="dark"] plutôt qu'une classe .dark-mode ?
Découvrir la réponse
L'utilisation d'attributs de données (data-*) est souvent préférée pour les états d'application. Cela permet de stocker plus d'informations (ex: data-theme="forest", data-theme="ocean") et évite les conflits avec les classes de style purement visuelles. C'est également plus facile à cibler via des sélecteurs CSS précis.
3. Comment gérer la transition visuelle entre deux thèmes pour éviter un flash brutal ?
Découvrir la réponse
On utilise la propriété transition sur les propriétés qui utilisent les variables (comme background-color et color). Il est crucial de définir une durée et une courbe de transition (ex: transition: background-color 0.3s ease) sur les éléments concernés.
4. Peut-on utiliser une variable CSS comme valeur par défaut dans var() ?
Découvrir la réponse
Oui, la fonction var() accepte un deuxième argument qui sert de valeur de secours (fallback) si la variable n'est pas définie : color: var(--main-color, black);. Ici, si --main-color est indéfinie, le texte sera noir.
5. Est-il possible de modifier une variable CSS via JavaScript ?
Découvrir la réponse
Oui, en utilisant la méthode setProperty sur l'objet style d'un élément. Pour modifier une variable globale :
document.documentElement.style.setProperty('--primary-color', '#ff0000');
Mise en pratique
Exercice 1 : Reproduction guidée
Objectif : Créer un bouton dont la couleur change selon un attribut data-theme sur le body.
- Définissez deux variables dans
:root:--btn-bg(bleu) et--btn-text(blanc). - Créez un sélecteur
[data-theme="dark"]qui redéfinit--btn-bgen gris foncé et--btn-texten jaune. - Appliquez ces variables à un bouton
.btn-theme.
Découvrir la solution commentée
:root {
--btn-bg: #007bff;
--btn-text: #ffffff;
}
[data-theme="dark"] {
--btn-bg: #343a40;
--btn-text: #ffeb3b;
}
.btn-theme {
background-color: var(--btn-bg);
color: var(--btn-text);
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
Exercice 2 : Adaptation et Scope
Objectif : Implémenter un système de "Cartes de prix" où chaque carte peut avoir son propre accent, tout en respectant le thème global.
HTML de départ :
<body data-theme="light"> <!-- Changez en "dark" pour tester -->
<div class="price-card card-basic">
<h3>Plan Basique</h3>
<p class="price-amount">9 €<span>/mois</span></p>
<p>Accès limité aux fonctionnalités</p>
</div>
<div class="price-card card-premium">
<h3>Plan Premium</h3>
<p class="price-amount">29 €<span>/mois</span></p>
<p>Accès illimité à toutes les fonctionnalités</p>
</div>
</body>
CSS de départ (à compléter) :
:root {
--bg-card: #ffffff;
--text-main: #333333;
/* À vous d'ajouter --accent-color et sa valeur par défaut */
}
[data-theme="dark"] {
/* À vous de redéfinir --bg-card et --text-main pour le mode sombre */
}
.price-card {
background-color: var(--bg-card);
color: var(--text-main);
border: 1px solid #ccc;
padding: 20px;
text-align: center;
}
.price-amount {
font-size: 2rem;
font-weight: bold;
/* À vous d'utiliser --accent-color ici */
}
/* À vous de créer .card-basic et .card-premium
en redéfinissant --accent-color localement */
Consignes :
- Ajoutez
--accent-colordans:root(valeur par défaut : gris#999999). - Remplissez
[data-theme="dark"]pour adapter fond (#1e1e1e) et texte (#f0f0f0). - Utilisez
--accent-colorsur.price-amount. - Créez
.card-basic(accent#7f8c8d) et.card-premium(accent#f1c40f, bordure de la couleur de l'accent).
Découvrir la solution commentée
:root {
--bg-card: #ffffff;
--text-main: #333333;
--accent-color: #999999; /* Valeur par défaut */
}
[data-theme="dark"] {
--bg-card: #1e1e1e;
--text-main: #f0f0f0;
}
.price-card {
background-color: var(--bg-card);
color: var(--text-main);
border: 1px solid #ccc;
padding: 20px;
text-align: center;
}
.price-amount {
color: var(--accent-color);
font-size: 2rem;
font-weight: bold;
}
/* Surcharge locale : on ne change que la variable, pas le style */
.card-basic {
--accent-color: #7f8c8d;
}
.card-premium {
--accent-color: #f1c40f;
border: 2px solid var(--accent-color);
}
Exercice 3 : Conception de Dashboard
Objectif : Concevoir un système de thémage pour un dashboard avec trois thèmes : light, dark, et high-contrast.
HTML de départ :
<body data-theme="light">
<!-- Changez data-theme en "dark" ou "high-contrast" pour tester les thèmes -->
<div class="dashboard-card">
<h2 class="brand-text">Tableau de bord</h2>
<p>Bienvenue sur votre espace de travail.</p>
</div>
<div class="dashboard-card">
<h3>Statistiques du jour</h3>
<p>42 nouvelles entrées enregistrées.</p>
</div>
</body>
Pour tester vos trois thèmes, modifiez directement l'attribut data-theme dans le HTML :
<body data-theme="light">→ thème clair<body data-theme="dark">→ thème sombre<body data-theme="high-contrast">→ thème contrasté
En production, ce changement se fait en JavaScript :
document.body.setAttribute('data-theme', 'dark');
Consignes :
- Architecturez vos variables en deux couches :
- Primitives : Couleurs brutes (ex:
--color-blue-500: #3b82f6). - Sémantiques : Rôles (ex:
--brand-color: var(--color-blue-500)).
- Primitives : Couleurs brutes (ex:
- Implémentez les trois thèmes via
[data-theme="..."]. - Le thème
high-contrastdoit forcer des bordures épaisses et des couleurs noir/blanc pur. - Ajoutez une transition fluide sur le changement de thème.
Découvrir la solution commentée
:root {
/* Primitives - Ne changent jamais selon le thème */
--white: #ffffff;
--black: #000000;
--blue-500: #3b82f6;
--blue-900: #1e3a8a;
--gray-100: #f3f4f6;
--gray-800: #1f2937;
--yellow-400: #facc15;
/* Sémantique - Thème Light (Default) */
--bg-app: var(--gray-100);
--bg-surface: var(--white);
--text-main: var(--gray-800);
--brand: var(--blue-500);
--border-width: 1px;
--border-color: #ddd;
--transition-speed: 0.3s;
}
[data-theme="dark"] {
--bg-app: var(--gray-800);
--bg-surface: var(--blue-900);
--text-main: var(--white);
--brand: var(--blue-500);
--border-color: #444;
}
[data-theme="high-contrast"] {
--bg-app: var(--black);
--bg-surface: var(--black);
--text-main: var(--white);
--brand: var(--yellow-400);
--border-width: 3px;
--border-color: var(--white);
}
body {
background-color: var(--bg-app);
color: var(--text-main);
transition: all var(--transition-speed) ease;
}
.dashboard-card {
background-color: var(--bg-surface);
color: var(--text-main);
border: var(--border-width) solid var(--border-color);
padding: 1.5rem;
transition: all var(--transition-speed) ease;
}
.brand-text {
color: var(--brand);
font-weight: bold;
}