Chapitre 13 : Portée et scoping des variables CSS
Concepts clés : Définition locale vs globale et héritage dans le DOM
L'un des plus grands pouvoirs des variables CSS (officiellement appelées Custom Properties) réside dans leur capacité à suivre la cascade du CSS. Contrairement aux variables de préprocesseurs comme Sass ou Less, qui sont résolues lors de la compilation, les variables CSS sont dynamiques et dépendent de la structure de votre document HTML.
La Portée des Variables CSS
1. Quoi
La portée (ou scope) d'une variable CSS détermine où cette variable est accessible et utilisable dans votre document. On distingue principalement deux types de portée :
- La portée globale : La variable est définie sur l'élément racine (généralement via le pseudo-sélecteur
:root). Elle est accessible par absolument tous les éléments de la page. - La portée locale : La variable est définie sur un sélecteur spécifique (une classe, un ID ou une balise). Elle n'est accessible que par cet élément et tous ses descendants.
2. Pourquoi
Dans un projet professionnel, gérer la portée permet de créer des composants modulaires et réutilisables. Imaginez un composant "Carte" (.card). Vous voulez que toutes les cartes aient un style par défaut, mais vous souhaitez pouvoir changer la couleur d'une seule carte spécifique sans avoir à créer une nouvelle classe CSS complexe pour chaque variante.
Le scoping permet de :
- Éviter les conflits : Une variable locale ne polluera pas le reste de l'application.
- Faciliter le thémage : On peut changer l'apparence d'une section entière en redéfinissant une variable au sommet de cette section.
- Réduire la duplication : On utilise la même variable partout, mais on change sa valeur selon le contexte.
3. Comment
A. Syntaxe de base
La différence réside uniquement dans le sélecteur utilisé pour la déclaration.
/* Portée Globale */
:root {
--main-color: #3498db;
}
/* Portée Locale */
.sidebar {
--main-color: #2c3e50;
}
B. Cas concret : Le système de thèmes de composants
Voici un exemple robuste où nous définissons des couleurs globales, mais permettons à un composant spécifique de "surcharger" ces valeurs pour ses propres besoins.
/* 1. Design Tokens Globaux */
:root {
--brand-primary: #6200ee;
--brand-secondary: #03dac6;
--text-color: #333333;
--bg-color: #ffffff;
--border-radius: 8px;
}
/* 2. Style de base du composant Bouton */
.btn {
background-color: var(--brand-primary);
color: var(--bg-color);
padding: 10px 20px;
border-radius: var(--border-radius);
border: none;
cursor: pointer;
transition: background-color 0.3s ease;
}
/* 3. Scoping pour une zone spécifique (ex: le Footer) */
.main-footer {
--brand-primary: #222222; /* On redéfinit la variable localement */
--bg-color: #f5f5f5;
background-color: var(--bg-color);
padding: 2rem;
}
/*
Le bouton situé à l'intérieur de .main-footer utilisera
automatiquement la valeur #222222 car il hérite de la
portée locale du parent.
*/
C. Limitations
Il est important de noter que :
- L'héritage est unidirectionnel : Un parent ne peut pas accéder à une variable définie dans un enfant.
- La spécificité s'applique : Si vous définissez une variable dans
:rootet une autre dans.card, c'est la valeur la plus "proche" de l'élément dans l'arbre DOM qui sera utilisée. - Pas de calculs dynamiques complexes : Bien que
calc()fonctionne avec les variables, vous ne pouvez pas redéfinir une variable en fonction d'elle-même (cela créerait une boucle infinie).
4. Zone de Danger
❌ Erreur commune : Déclarer toutes les variables dans :root
Certains développeurs créent des variables comme --card-1-color, --card-2-color, etc. Cela rend le code rigide et difficile à maintenir.
✅ Bonne pratique : Utiliser des noms génériques et le scoping
Utilisez --card-color et redéfinissez-la dans le sélecteur de la carte spécifique.
/* MAUVAIS */
:root {
--card-blue-bg: blue;
--card-red-bg: red;
}
.card-blue { background: var(--card-blue-bg); }
.card-red { background: var(--card-red-bg); }
/* BIEN */
:root {
--card-bg: white; /* Valeur par défaut */
}
.card { background: var(--card-bg); }
.card-blue { --card-bg: blue; } /* Redéfinition locale */
.card-red { --card-bg: red; } /* Redéfinition locale */
Cascade et Scoping des Variables CSS
Questions clés
1. Quelle est la différence fondamentale entre :root et un sélecteur de classe pour une variable ?
Découvrir la réponse
:root cible l'élément le plus haut de la hiérarchie (l'élément <html>), rendant la variable disponible pour tout le document. Un sélecteur de classe limite la disponibilité de la variable à cet élément et à ses descendants.
2. Que se passe-t-il si une variable est définie à la fois dans :root et dans .container ?
Découvrir la réponse
Pour tout élément situé à l'intérieur de .container, la valeur définie dans .container sera prioritaire car elle est plus spécifique et plus proche dans l'arbre DOM. Pour les éléments en dehors de .container, c'est la valeur de :root qui sera utilisée.
3. Est-ce qu'une variable définie dans un élément enfant peut modifier le style d'un élément parent ?
Découvrir la réponse
Non. Les variables CSS suivent le flux de l'héritage DOM (du parent vers l'enfant). Un parent ne peut pas "voir" les variables définies dans ses enfants.
4. Pourquoi utiliser le scoping plutôt que de créer plusieurs classes de couleurs ?
Découvrir la réponse
Le scoping permet de maintenir un code CSS plus propre. Au lieu de créer .btn-primary, .btn-secondary, .btn-danger avec des propriétés répétées, on crée un seul .btn qui utilise une variable, et on change simplement la valeur de cette variable selon le contexte.
Mise en pratique
Exercice 1 : Reproduction guidée
Objectif : Créer un système de couleurs simple avec une portée globale et une portée locale.
- Créez une variable
--text-colordans:rootavec la valeurblack. - Appliquez cette variable au corps de la page (
body). - Créez une classe
.dark-sectionqui redéfinit--text-colorenwhiteet possède un fond noir. - Vérifiez que le texte à l'intérieur de
.dark-sectiondevient blanc automatiquement.
Découvrir la solution commentée
/* Définition globale */
:root {
--text-color: black;
}
body {
color: var(--text-color);
}
/* Redéfinition locale pour le scoping */
.dark-section {
--text-color: white; /* La variable est écrasée pour cet élément et ses enfants */
/* color doit être re-déclaré ici : body a déjà calculé sa couleur avec
l'ancienne valeur de la variable. Re-déclarer force le navigateur à
évaluer var(--text-color) dans ce nouveau scope. */
color: var(--text-color);
background-color: black;
padding: 20px;
}
Exercice 2 : Adaptation
Objectif : Transformer un système de classes rigides en système de variables scopées.
HTML de départ :
<div class="alert alert-success">Opération réussie !</div>
<div class="alert alert-error">Une erreur s'est produite.</div>
CSS de départ (à refactoriser) :
.alert { padding: 10px; border: 1px solid; }
.alert-success { color: green; border-color: green; }
.alert-error { color: red; border-color: red; }
Modifiez ce CSS pour utiliser une seule variable --alert-color définie localement dans chaque variante.
Découvrir la solution commentée
/* Style de base utilisant la variable */
.alert {
padding: 10px;
border: 1px solid var(--alert-color);
color: var(--alert-color);
}
/* On ne définit que la valeur de la variable pour chaque variante */
.alert-success {
--alert-color: green;
}
.alert-error {
--alert-color: red;
}
Exercice 3 : Conception
Objectif : Créer un système de "Thèmes de Cartes" pour un tableau de bord.
Vous devez créer un composant .card.
- Par défaut, la carte a une bordure grise et un titre bleu.
- Si la carte a la classe
.card-featured, la bordure doit devenir dorée et le titre orange. - Si la carte est placée à l'intérieur d'un conteneur
.dashboard-dark, toutes les cartes à l'intérieur (même celles qui ne sont pas "featured") doivent avoir un fond sombre et un texte clair. - Créez une classe
.card-containerutilisant Flexbox en colonne avec ungapde1rempour espacer les cartes.
HTML de départ :
<div class="card-container">
<div class="card">
<h3>Test 1</h3>
<p>Lorem ipsum</p>
</div>
<div class="card card-featured">
<h3>Test 2</h3>
<p>Lorem ipsum</p>
</div>
<div class="dashboard-dark card-container">
<div class="card">
<h3>Test 3 (dark)</h3>
<p>Lorem ipsum</p>
</div>
<div class="card card-featured">
<h3>Test 4 (dark + featured)</h3>
<p>Lorem ipsum</p>
</div>
</div>
</div>
Découvrir la solution commentée
:root {
--card-bg: #ffffff;
--card-border: #cccccc;
--card-title-color: #0000ff;
--card-text-color: #333333;
}
.card-container {
display: flex;
flex-direction: column;
gap: 1rem;
}
/* Conteneur de thème sombre : redéfinit les variables pour tous ses descendants */
.dashboard-dark {
--card-bg: #222222;
--card-text-color: #eeeeee;
--card-border: #444444;
}
.card {
background-color: var(--card-bg);
border: 2px solid var(--card-border);
color: var(--card-text-color);
padding: 1rem;
}
.card h3 {
color: var(--card-title-color);
}
.card p {
color: var(--card-text-color);
}
/* Variante spécifique : redéfinit uniquement les variables de bordure et de titre */
.card-featured {
--card-border: gold;
--card-title-color: orange;
}