Chapitre 37 : Arbitrage : Variables CSS natives vs Préprocesseurs
Comparaison des capacités de calcul, de portée et de cycle de vie entre les variables Sass et les Custom Properties CSS.
Dans l'écosystème moderne du développement front-end, on se retrouve souvent avec un mélange de préprocesseurs (comme Sass) et de CSS natif. L'une des questions les plus fréquentes pour un développeur intermédiaire est : "Pourquoi utiliser des variables Sass ($var) alors que nous avons maintenant des variables CSS (--var) ?".
La réponse ne réside pas dans le fait que l'une remplace l'autre, mais dans le moment où elles sont traitées : l'une est une instruction de compilation, l'autre est une instruction d'exécution.
Le duel : Compile-time vs Runtime
1. Quoi
Pour comprendre l'arbitrage, il faut définir deux concepts fondamentaux :
- Compile-time (Sass) : Les variables Sass (
$variable) existent uniquement pendant que Sass transforme votre code.scssen fichier.css. Une fois le fichier généré, les variables disparaissent et sont remplacées par leurs valeurs finales. Le navigateur ne voit jamais le symbole$. - Runtime (CSS natif) : Les Custom Properties (
--variable) sont natives au navigateur. Elles existent dans le DOM, sont liées à l'arbre des éléments et peuvent être modifiées en temps réel via JavaScript ou des Media Queries.
2. Pourquoi
Le choix entre les deux impacte directement la flexibilité de votre interface et la performance de votre build.
- Utilisez Sass pour des constantes structurelles qui ne changeront jamais durant la session utilisateur (ex: grille de base, breakpoints, configurations de mixins).
- Utilisez CSS natif pour tout ce qui touche au Theming (mode sombre/clair), aux animations dynamiques, ou aux valeurs qui dépendent du contexte de l'utilisateur.
3. Comment
A. Syntaxe de base
// Sass : Statique, disparaît après compilation
$primary-color: #3498db;
.button {
background-color: $primary-color;
}
// CSS Natif : Dynamique, persiste dans le navigateur
:root {
--primary-color: #3498db;
}
.button {
background-color: var(--primary-color);
}
B. Cas concret : Système de Thème Hybride
Dans un projet professionnel, la meilleure approche est souvent l'hybridation. On utilise Sass pour gérer la logique de génération et le CSS natif pour l'application du thème.
// _variables.scss
// On définit des "tokens" de design en Sass pour la maintenance
$palette-blue-500: #3498db;
$palette-blue-700: #2980b9;
$palette-grey-100: #f4f4f4;
// On injecte ces tokens dans des variables CSS pour le runtime
:root {
// Pourquoi #{} et pas directement $palette-blue-500 ?
// Les propriétés CSS custom (--var) acceptent n'importe quelle valeur brute :
// Sass les traite comme du texte et N'évalue PAS les variables $var à l'intérieur.
// La syntaxe #{$var} est l'interpolation Sass : elle force Sass à substituer
// la valeur de $palette-blue-500 AVANT de produire le CSS final.
// Sans #{}, le CSS généré serait --color-primary: $palette-blue-500; (invalide).
--color-primary: #{$palette-blue-500};
--color-primary-dark: #{$palette-blue-700};
--bg-main: #{$palette-grey-100};
--transition-speed: 0.3s;
}
// Thème sombre : on ne change que les valeurs CSS
[data-theme="dark"] {
--color-primary: #5dade2;
--bg-main: #2c3e50;
}
.card {
background-color: var(--bg-main);
border: 1px solid var(--color-primary);
transition: background-color var(--transition-speed) ease;
&:hover {
background-color: var(--color-primary-dark);
}
}
C. Limitations
| Caractéristique | Variables Sass ($) | Variables CSS (--) |
|---|---|---|
| Accès JS | Impossible (non présentes dans le CSS) | Possible via getPropertyValue() |
| Portée (Scope) | Lexicale (bloc de code) | DOM (Cascade CSS) |
| Calculs | Puissants (darken(), lighten(), mix()) | Limités (calc()) |
| Performance | Zéro impact runtime | Très léger impact sur le rendu (re-paint) |
| Media Queries | Ne peuvent pas être changées dedans | Peuvent être redéfinies par breakpoint |
4. Zone de Danger
❌ L'erreur commune : Tenter de modifier une variable Sass via JavaScript.
Pourquoi ? Parce que la variable Sass a été remplacée par sa valeur lors de la compilation. Le navigateur ne connaît pas $primary-color.
✅ La bonne pratique : Utiliser Sass pour les calculs complexes et les constantes de build, et mapper ces résultats vers des Custom Properties pour tout ce qui doit être dynamique.
Arbre de décision : Sass vs CSS Variables
Analyse approfondie : Portée et Cascade
L'un des points les plus critiques de l'arbitrage est la gestion de la portée.
La portée Sass (Lexicale)
Une variable Sass est disponible là où elle est déclarée et dans tous les blocs imbriqués. Si vous déclarez une variable à l'intérieur d'un sélecteur, elle n'est pas disponible en dehors de ce bloc. C'est une portée de "code".
La portée CSS (DOM)
Une Custom Property suit la cascade CSS. Si vous définissez --color: red sur body, tous les enfants en héritent. Mais si vous redéfinissez --color: blue sur un .card, seul cet élément et ses propres enfants utiliseront le bleu. C'est une portée d' "élément".
Exemple de puissance du runtime CSS :
.container {
--local-accent: blue;
}
.container .item {
color: var(--local-accent); /* Sera bleu */
}
.container .item.special {
--local-accent: gold; /* Redéfinition locale */
color: var(--local-accent); /* Sera or */
}
Faire cela avec Sass nécessiterait de créer des classes spécifiques (.item--gold) pour chaque variation, augmentant ainsi la taille du fichier CSS.
Questions clés
1. Puis-je utiliser une variable CSS à l'intérieur d'une fonction Sass comme lighten() ?
Découvrir la réponse
Non. Les fonctions Sass s'exécutent lors de la compilation. À ce moment-là, la variable CSS var(--color) est juste une chaîne de caractères pour Sass, et non une couleur réelle. Sass ne peut pas "calculer" une valeur que seul le navigateur connaîtra plus tard.
2. Quel est l'impact sur la performance d'utiliser trop de variables CSS ?
Découvrir la réponse
L'impact est négligeable pour la majorité des sites. Cependant, modifier une variable définie sur :root peut forcer le navigateur à recalculer les styles de tout l'arbre DOM. Pour des animations très fréquentes (60fps), il est préférable de modifier la variable sur l'élément spécifique plutôt que sur la racine.
3. Pourquoi continuer à utiliser Sass si CSS devient si puissant ?
Découvrir la réponse
Sass apporte des outils de productivité que le CSS natif n'a pas encore (ou moins bien) : les mixins complexes, les boucles @for pour générer des classes utilitaires, et une organisation modulaire via @use et @forward.
4. Comment gérer les valeurs par défaut avec les variables CSS ?
Découvrir la réponse
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);.
Mise en pratique
Exercice 1 : Reproduction guidée
Créez un système simple où une couleur primaire est définie en Sass, puis assignée à une variable CSS dans :root. Changez cette couleur dans une media query @media (max-width: 600px).
Découvrir la solution commentée
// Variable de compilation (Sass)
$brand-blue: #007bff;
:root {
// #{} est obligatoire ici : sans lui, Sass ne substitue pas $brand-blue
// dans une propriété custom et le CSS généré serait invalide.
--primary-color: #{$brand-blue};
}
.header {
background-color: var(--primary-color);
}
// Le runtime CSS permet de changer la valeur selon le viewport,
// ce qu'une variable Sass seule ne pourrait pas faire.
@media (max-width: 600px) {
:root {
--primary-color: #0056b3; // Plus sombre sur mobile
}
}
Exercice 2 : Adaptation
Vous avez un composant Button. Transformez-le pour qu'il accepte une variable CSS --btn-color. Créez deux variantes de boutons (Primary et Secondary) en redéfinissant simplement cette variable CSS dans des classes distinctes, sans répéter les propriétés de padding, border et font.
HTML de départ :
<button class="btn btn-primary">Confirmer</button>
<button class="btn btn-secondary">Annuler</button>
<button class="btn">Neutre (sans variante)</button>
L'élément doit porter à la fois .btn (pour les propriétés de base) et .btn-primary ou .btn-secondary (pour la valeur de la variable). La variable --btn-color définie sur .btn-primary est accessible dans les règles .btn car les deux sélecteurs ciblent le même élément.
Découvrir la solution commentée
// On définit une valeur de repli dans :root pour le bouton sans variante.
// Sans cette ligne, un <button class="btn"> seul n'aurait ni couleur
// ni bordure visibles (var(--btn-color) serait invalide).
:root {
--btn-color: #555;
}
// Style de base du bouton — utilise la variable CSS
.btn {
padding: 0.5rem 1.25rem;
border: 2px solid var(--btn-color);
color: var(--btn-color);
background: transparent;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s ease, color 0.2s ease;
&:hover {
background-color: var(--btn-color);
color: white;
}
}
// On ne définit QUE la valeur de la variable pour chaque variante.
// Pas de padding, border ou font dupliqués !
.btn-primary {
--btn-color: #3498db;
}
.btn-secondary {
--btn-color: #95a5a6;
}
Exercice 3 : Conception
Concevez un système de "Theming" pour un tableau de bord.
- Utilisez Sass pour définir une palette de 5 nuances de gris.
- Mappez ces nuances vers des variables CSS (
--bg-light,--bg-medium,--bg-dark). - Implémentez un switch "Mode Sombre" via un attribut
data-theme="dark"sur lebodyqui inverse les mappings des variables CSS. - Ajoutez un effet de surbrillance dynamique via JavaScript qui modifie la variable
--accent-coloren fonction de la position de la souris (optionnel/conceptuel).
Découvrir la solution commentée
// 1. Palette de design (Sass — compile-time)
$grey-100: #f8f9fa;
$grey-300: #dee2e6;
$grey-500: #adb5bd;
$grey-700: #495057;
$grey-900: #212529;
// 2. Mapping Runtime (CSS custom properties)
// #{} est nécessaire pour que Sass injecte les valeurs dans les custom properties.
:root {
--bg-light: #{$grey-100};
--bg-medium: #{$grey-300};
--bg-dark: #{$grey-900};
--text-color: #{$grey-900};
--accent-color: #007bff;
}
// 3. Inversion du thème (uniquement le mapping change, pas les composants)
[data-theme="dark"] {
--bg-light: #{$grey-900};
--bg-medium: #{$grey-700};
--bg-dark: #{$grey-100};
--text-color: #{$grey-100};
}
// 4. Application — les composants ne connaissent pas le thème, seulement leurs variables
body {
background-color: var(--bg-light);
color: var(--text-color);
transition: background-color 0.3s ease, color 0.3s ease;
}
.sidebar {
background-color: var(--bg-medium);
border-right: 2px solid var(--accent-color);
}
.footer {
background-color: var(--bg-dark);
}