Les variables CSS sont nos amies

Les variables CSS sont géniales

Certains parlent de « variables CSS », d’autres parlent de « propriétés personnalisées CSS » (ou CSS custom properties en anglais). Il s’agit bien du même concept. Le terme propriétés personnalisées est souvent utilisé pour limiter les confusions, car ce ne sont pas de simples variables ! C’est leur fonctionnement qui fait mon émerveillement sur ce standard. Je pense que son implémentation changera considérablement nos bonnes pratiques. En tout cas, c’est ce sentiment que je vais essayer de partager avec vous dans cet article.

Au moment où j’écris ceci, ce standard est stabilisé (en « Last Call Working Draft ») depuis mai 2014. Mais il n’est implémenté que dans Firefox, et dans Chrome derrière un flag. Il s’agit donc d’un article pour les curieux. Vous trouverez peut-être ici de très bon conseils, mais vous ne pourrez pas les appliquer au quotidien avant 2020.

1. Introduction au fonctionnement des variables CSS

1.1 C’est quoi les variables CSS ?

On peut faire deux choses avec une variable : lui assigner une valeur, ou lire la valeur qu’elle contient. Pour cela, on doit respecter les règles de syntaxe suivantes :

  • Préfixer les variables par un double tiret « –- », pour les différencier des propriétés navigateur ;
  • Utiliser la fonction var() pour lire la valeur de la propriété personnalisée.
a {
    /* Déclaration d'une variable */
    --color-clickable: red;
    /* Utilisation de la variable CSS */
    color: var(--color-clickable);
}
/* Le code précédent est équivalent à */
a {
    color: red;
}

Jouer avec ce code en ligne (sur Firefox)

1.2 Les variables CSS sont définies pour chaque élément ciblé

La subtilité réside dans le fait que les variables CSS ne sont pas globales. Elles ne sont pas compilées à la lecture de la feuille de style non plus. Comme les propriétés navigateur, elles seront résolues pour chaque élément HTML auquel vous appliquez votre feuille de style. Les variables CSS ne seront définies que sur les éléments ciblés par le sélecteur CSS où elles sont déclarées.

Quand on parle de propriétés personnalisées, on est donc plus proche du fonctionnement interne. Chaque élément de votre page pourra définir la valeur de sa propriété personnalisée --color-clickable, de la même manière qu’il stocke la valeur de ses propriétés background ou margin.

/* Website styles */
a,
button {
    color: var(--color-clickable);
}
/* Theme styles */
a {
    --color-clickable: red;
}
button {
    --color-clickable: blue;
}
/* Le code précédent est équivalent à */
a {
    color: red;
}
button {
    color: blue;
}

Jouer avec ce code en ligne (sur Firefox)

Dans l’exemple précédent, les liens et les boutons utilisent leur valeur de la propriété personnalisée --color-clickable pour définir dans quelle couleur leur texte sera écrit. Pour connaître cette valeur, il faut résoudre la valeur de cette variable pour chaque élément.

Ici, les liens seront donc écrits en rouge, alors que les boutons seront écrits en bleu.

1.3 Surcharge d’une variable CSS

Les variables CSS nous permettent de déclarer et de modifier une propriété personnalisée à l’aide de deux sélecteurs qui n’ont pas de rapports évident entre eux. Par exemple, on peut déclarer une logique de manière générique, et adapter les paramètres à l’aide de sélecteurs plus précis.

a {
    --color-clickable: red;
    color: var(--color-clickable);
}
.pink {
    /* Modification d'une variable */
    --color-clickable: hotpink;
}
/* Le code précédent est équivalent à */
a {
    color: red;
}
a.pink {
    color: hotpink;
}

Jouer avec ce code en ligne (sur Firefox)

Dans cet exemple, on autorise la modification de la couleur d’un lien en particulier, à l’aide de la classe .pink. En changeant la valeur de sa propriété personnalisée, on ne changera pas celle des autres liens.

Ici, tous les liens seront donc écrits en rouge par défaut. Ceux à qui on aura appliqué la classe .pink seront en revanche écrits en rose.

Cette introduction devrait éclaircir mon affirmation de départ : « les variables CSS ne sont pas de simples variables ». Leur valeur et leur application sont fortement liées à la page HTML dans laquelle elles seront utilisées. Je me suis souvent demandé comment nommer ce système qui ne peut exister que dans nos feuilles de style. En regardant du côté du standard, on trouve le nom exact : « CSS Custom Properties for Cascading Variables », ou plus simplement « CSS Cascading Variables ».

Pour aller plus loin

Vous voulez comprendre comment fonctionnent les variables CSS ? Je vous conseille ces lectures :

2. La cascade, une ressource sous-estimée

Selon moi, le terme le plus explicite est donc « les variables en cascade CSS ». Avant d’aller plus loin, pour que ce terme soit vraiment clair entre nous, il vaut mieux qu’on se mette d’accord sur la définition de la cascade.

2.1 C’est quoi la cascade ?

La cascade fait partie du cœur du fonctionnement des feuilles de style. La cascade est même le premier mot de l’abréviation CSS. C’est un des concepts qui fait la force de ce langage, au point que vous l’utilisez sûrement tous les jours.

La plupart des propriétés, comme font-size ou color, s’écoulent en cascade dans le document HTML. C’est à dire qu’elles sont propagées des éléments parents aux enfants, tant qu’aucun élément ne se voit appliqué une nouvelle valeur. On parle aussi d’héritage pour expliquer ce mécanisme.

La cascade c’est pouvoir utiliser la valeur d’une propriété définie sur un élement, depuis un de ses descendants dans l’arborescence HTML.

html {
    font-size: 16px;
    color: hotpink;
}
h1 {
    font-size: 2em;
}

Jouer avec ce code en ligne

Ici, on utilise la cascade dans deux conditions différentes :

  • les titres de niveau 1 seront écrits en rose, puisqu’ils hériteront de la couleur de texte définie par l’élément racine.
  • les titres de niveau 1 auront une police d’écriture deux fois plus grande que le reste du document, car ils définissent leur font-size en fonction de celle héritée.

Il y a quand même un risque qu’un élément entre l’élément racine et le titre de niveau 1 définisse une autre taille de police. Dans ce cas, les titres n’auront pas la taille à laquelle on s’attend. Alors pourquoi utiliser la cascade ? Est-ce vraiment une bonne idée ?

2.2 Dans un monde triste sans cascade

Imaginons un monde sans cascade, les valeurs par défaut ne pourraient être définies qu’à l’aide du sélecteur universel *.

* {
    font-size: 16px;
    color: hotpink
}
h1 {
    font-size: 2em;
}
<h1><span>Cascading variables are awesome</span></h1>

Jouer avec ce code en ligne

Ici, on s’attend à ce que les titres de niveau 1 aient une taille de police deux fois plus grande que le reste du texte. Pourtant, si l’on ajoute à l’intérieur de notre titre une balise span, nous réinitialisons la taille de la police à sa valeur par défaut.

La cascade est utile. Elle nous permet d’avoir plus de souplesse dans la structure de nos documents. Par exemple, on peut facilement modifier la couleur d’écriture sur un pied de page, sans modifier la couleur de chaque élément qui le compose.

Pour aller plus loin

Vous voulez comprendre la cascade en CSS et comment jouer avec ? Je vous conseille ces lectures :

3. La spécificité : la limite de la cascade

J’ai donc l’intime conviction que la cascade est une très bonne idée pour appliquer des règles CSS sur une partie de document. Cela permet de faire des règles de gestion globales et ainsi d’éviter la répétition de code.

Malheureusement il y a une ombre au tableau. Ses pouvoirs sont jumelés avec ceux de la spécificité. Du coup, de nombreuses bonnes pratiques actuelles déconseillent de trop l’utiliser pour gagner en simplicité.

3.1 C’est quoi la spécificité ?

La spécificité est le système utilisé pour prioriser les règles CSS entre elles. Ce système est la cause de nombreux bugs d’intégration, car il demande d’avoir une vision globale du projet pour savoir si une règle CSS va être appliquée ou non.

À cause de la cascade, un parent ne peut jamais surcharger les propriétés définies directement sur ses enfants. Ainsi, pour propager certaines valeurs dans une hiérarchie, on doit sélectionner des éléments par leur ascendance. À cause de la spécificité, ce type de sélecteurs devient prioritaire sur ses camarades.

/* Default styles */
a {
    color: red;
}
/* Footer styles */
.footer {
    background: black;
    color: white;
}
.footer a {
    color: lightgrey;
}
/* Component styles */
.button {
    color: black;
    background: lightgrey;
}

Jouer avec ce code en ligne

Ici, par défaut, le texte est écrit en noir sur blanc, sauf les liens qui seront rouges.

Le pied de page étant sombre, on redéfinit la couleur du texte en blanc. On doit aussi surcharger la couleur des liens qui sont à l’intérieur du pied de page pour utiliser une couleur qui contraste plus. Pour cela, notre sélecteur .footer a sera composé d’une classe et d’une balise. Cette règle sera donc prioritaire sur toutes les autres qui ne sont composées que d’une classe ou d’une balise.

En parallèle, nous avons également construit des composants réutilisables facilement, comme des boutons. Ces boutons sont normalement écrits en noir sur gris clair. Néanmoins, si on utilise l’un de ces boutons dans le pied de page à l’aide d’une balise lien, la couleur d’écriture sera surchargée par la règle de descendance avec l’élément .footer. Le bouton ne sera pas lisible, puisque l’écriture sera en gris sur un fond gris.

3.2 Utiliser la cascade sans spécificité avec les variables CSS

Les conflits de spécificité entre les surcharges en fonction du contexte et les composants autonomes sont courants. Il s’agit d’un problème que l’on rencontre régulièrement lors des intégrations. Les bonnes pratiques actuelles, telles que BEM ou les classes atomiques, ont su répondre à cette problématique en n’utilisant pas la spécificité des sélecteurs. En faisant ce choix, ils ont également limité considérablement l’utilisation de la cascade.

Heureusement, avec les variables CSS, vous pouvez utiliser la cascade sans augmenter la spécificité de vos sélecteurs.

/* Default styles */
html {
    --color-clickable: red;
}
a {
    color: var(--color-clickable);
}
/* Footer styles */
.footer {
    --color-clickable: lightgrey;
    background: black;
    color: white;
}
/* Component styles */
.button {
    color: black;
    background: lightgrey;
}

Jouer avec ce code en ligne (sur Firefox)

Ici, on surcharge la valeur de la propriété --color-clickable pour l’arborescence du pied de page. Ainsi tous les liens de cette section seront écrits en clair sur fond noir, car leur couleur est calculée en fonction de cette variable.

Les boutons, en revanche, resteront avec leur couleur d’origine, car cette dernière n’est pas calculée en fonction d’une variable. Nous noterons que la spécificité des sélecteurs est moins forte. Il n’y a plus de super sélecteur avec une classe et une balise pour surcharger la couleur des boutons.

Pour aller plus loin

Vous voulez comprendre le fonctionnement de la spécificité en CSS et comment la limiter ? Je vous conseille ces lectures :

4. Les variables CSS et BEM

Dans la section précédente, j’ai mis en opposition la puissance des variables CSS et l’intelligence des bonnes pratiques telles que BEM. Ce n’est pas nécessaire, les propriétés personnalisées peuvent également améliorer la qualité des intégrations BEM.

4.1 C’est quoi BEM ?

BEM est un standard de nommage qui permet de diminuer la spécificité des sélecteurs. Ainsi, par convention, nous allons remplacer les opérateurs dans nos sélecteurs, par des noms de classes plus précis.

/* Les classes modifiantes avec... */
/* Zen CSS: concaténation */
.button.red { }
/* Convention BEM: "--" */
.button--red { }
/* Les classes de l'élement avec... */
/* Zen CSS: sélecteur d'enfant  */
.button .icon { }
/* Convention BEM: "__" */
.button__icon { }

Cela fait écrire des noms de classes un peu plus longs dans nos documents HTML, mais c’est très efficace pour réduire la spécificité des sélecteurs.

4.2 Réduire la spécificité des intégrations BEM

Lorsque les classes modifiantes ont un effet sur des éléments enfants, nous sommes obligés d’avoir recours aux sélecteurs enfants, même en BEM.

/* Base button component */
.button {
    color: black;
}
.button__icon {
    color: blue;
}
/* Pink modifier */
.button--pink .button__icon {
    color: hotpink;
}

Jouer avec ce code en ligne

Dans cet exemple, nous avons une classe modifiante .button--pink sur notre élément bouton. La logique orientée composant nous invite à appliquer ce changement d’état sur son élement racine.

Ainsi pour appliquer les changements d’états aux élements enfants, je vais utiliser des sélecteurs spécifiques. Une solution contraire aux objectifs originaux des conventions BEM.

/* Base button component */
.button { 
    --color-button: blue;
    color: black;
}
.button__icon {
    color: var(--color-button);
}
/* Pink modifier */
.button--pink {
    --color-button: hotpink;
}

Avec les variables CSS, nous pouvons réellement stocker les états d’un composant au niveau des propriétés de son élément racine. Nous pouvons ensuite utiliser ces états dans les éléments enfants du composant. En plus d’obtenir un code correspondant plus à ce que l’on voulait faire, nous limitons les sélecteurs avec une forte spécificité.

4.3 Réduire la duplication de code des intégrations BEM

Les classes modifiantes créent de la duplication de code.

/* Base button component */
.button {
    color: black;
    border: 2px solid blue;
}
.button::before {
    content: '+';
    color: blue;
}
/* Pink modifier */
.button--pink {
    border-color: hotpink;
}
.button--pink::before {
    color: hotpink;
}

Jouer avec ce code en ligne

Dans cet exemple, notre code a plusieurs duplications :

  • Pour chaque classe modifiante, je dois répéter la couleur cible pour l’élément et le pseudo-élément ;
  • Dans chaque classe modifiante, je duplique une partie de l’implémentation du bouton : border-color et color.
/* Base button component */
.button {
    --color-button: blue; 
    color: black;
    border: 2px solid var(--color-button);
}
.button::before {
    content: '+';
    color: var(--color-button);
}
/* Pink Modifier */
.button--pink {
    --color-button: hotpink; 
}

Jouer avec ce code en ligne (sur Firefox)

C’est à la fois plus court et plus modulaire, car la déclaration des classes modifiantes est désormais indépendante de l’implémentation. Dans ces conditions, on ne prend plus peur lors de la production d’une dizaine de classes pour modifier la couleur des boutons.

Pour aller plus loin

Vous voulez comprendre ce qu’est BEM et pourquoi on l’utilise ? Je vous conseille ces lectures :

5. Les variables CSS et les classes atomiques

5.1 C’est quoi les classes atomiques ?

Les classes atomiques sont une autre tentative de réponse aux problèmes de maintenabilité engendré par la spécificité. L’objectif est d’oublier la sémantique lorsque cette couche d’abstraction semble inutile. Ainsi la compréhension du style désiré sera accessible dès la lecture de l’HTML.

Pour y arriver, on va notamment séparer les problématiques de positionnement, de dimensionnement, de marge, de bordure et de police dans des règles différentes. Ainsi on n’aura plus qu’à composer en piochant dans notre bibliothèque de classes unitaires lors de l’intégration.

<!-- Zen CSS in HTML -->
<div class="product-item highlighted"></div>
<!-- Atomic CSS in HTML -->
<div class="inline-block marge-medium border-thin border-yellow"></div>

5.2 Gérer les interactions des classes atomiques avec les variables CSS

Lorsque l’on intègre tout un site avec des classes atomiques, on est parfois limité par les interactions que deux classes peuvent avoir.

.grid > * {
    float: left;
    /* 5px gutter */
    margin-right: 5px;
    /* 2 columns */
    width: calc(100% / 2  - 5px);
}
.col-3 > * {
    width: calc(100% / 3 - 5px);
}

Jouer avec ce code en ligne (sur Firefox)

Dans cet exemple, nous contruisons une grille pour gérer le positionnement des éléments. Nous avons aussi des classes pour spécifier la dimension des cellules. Par contre, il est compliqué d’avoir une classe pour gérer les marges entre les cellules, puisque cette marge est utilisée dans les calculs des dimensions.

.grid {
    --grid-column: 2;
    --grid-gutter: 5px;
}
.grid > * {
    float: left;
    margin-right: var(--grid-gutter);
    width: calc(100% / var(--grid-column) - var(--grid-gutter));
}
.col-3 {
    --grid-column: 3;
}
.no-gutter {
    --grid-gutter: 0;
}

Jouer avec ce code en ligne (sur Firefox)

Avec les variables CSS nous pouvons gérer les interactions entre les classes atomiques qui gèrent le positionnement, le dimensionnement et les marges. Comme pour le code BEM, je trouve le code en résultant plus léger, plus clair et il réprésente mieux ce que je voulais faire.

Pour aller plus loin

Vous voulez savoir si les classes atomiques sont vraiment utiles ? Je vous conseille ces lectures :

Bilan

Nous avons vu ensemble des cas d’utilisation variés pour les variables en cascade CSS. J’espère que ces exemples simplifiés vous ont permis d’entrevoir les possibilités de ce nouveau standard. Selon moi, le plus gros avantage sera de pouvoir utiliser la cascade sans modifier les sélecteurs, car il s’agit de quelque chose qui nous est impossible pour l’instant !

Mais il y aura aussi plein d’autres avantages : on limite la duplication de code ; on simplifie la création d’intégrations personnalisables ; on gagne en lisibilité en pouvant nommer certaines valeurs ; on gagne en lisibilité en séparant la déclaration des calculs calc() de leur utilisation ; etc.

Il s’agit d’un tout nouveau monde à explorer ! Vivement le futur !

2 commentaires sur cet article

  1. MoOx, le 14 décembre 2015 à 7:18

    C'est marrant car personnellement, plus je code de CSS (et dieu sait que j'aime styler les applications sur lequel je travaille), plus je haie la cascade.

    Comme tu l'indiques on a trouvé des moyens (faute de mieux) pour éviter de s’embêter avec la spécificité (coucou bem... et les styles inlines) et je pense qu'on va arriver à un moment ou plutôt que de surcharger via la cascade (hum coucou les styles inlines?), on va prendre le problème en amont...

    Ah mais on me dit que via du "préprocessing" (enfin un traitement ou une façon de coder durant le développement) on peut déjà le faire pour éviter de surcharger les styles finaux (vu que finalement, le développeur est sensé maitriser son rendu)...

    Plus je code, plus je me dit que le problème de CSS c'est les selecteurs... Moins j'en code, mieux je me porte. Dans ce sens, je pense vraiment que les variables CSS supportant la cascade sont une balle perdue...

  2. Thomas Zilliox, le 14 décembre 2015 à 14:32

    @MoOx Est-ce que tu haies la cascade ou la spécificité ?

    Je crois beaucoup dans les styles en ligne également, cela règle le problème d'abstraction, de spécificité, etc. Elle pose par contre d'autres problèmes, comme le responsif.

    Je ne pense pas pour autant, que ce sera une solution universelle. Lorsque l'intégration est séparé du reste de l'applicatif, un prestataire ou différentes équipes, les styles en lignes auront du mal à prendre. De même si un site n'est pas fait en JavaScript, les styles en lignes seront verbeux et contraignants.

    Cette technique ne s'adaptera facilement qu'aux projets centrés autour du JS avec des équipes techniques non-divisées. Donc... Je pense que 80% des projets ne l'adopteront pas. A l'inverse, les gros projets très visibles comme ceux de Google ou Facebook, nous donnerons sûrement une autre impression ;)

Il n’est plus possible de laisser un commentaire sur les articles mais la discussion continue sur les réseaux sociaux :