Skip to main content

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

  1. 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.
  2. 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.
  3. 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.

  1. Définissez deux variables dans :root : --btn-bg (bleu) et --btn-text (blanc).
  2. Créez un sélecteur [data-theme="dark"] qui redéfinit --btn-bg en gris foncé et --btn-text en jaune.
  3. 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 :

  1. Ajoutez --accent-color dans :root (valeur par défaut : gris #999999).
  2. Remplissez [data-theme="dark"] pour adapter fond (#1e1e1e) et texte (#f0f0f0).
  3. Utilisez --accent-color sur .price-amount.
  4. 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>
Changer de thème sans JavaScript

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 :

  1. 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)).
  2. Implémentez les trois thèmes via [data-theme="..."].
  3. Le thème high-contrast doit forcer des bordures épaisses et des couleurs noir/blanc pur.
  4. 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;
}