Un guide stratégique pour les propriétés personnalisées CSS

Publié: 2022-03-10
Résumé rapide ↬ Les propriétés dynamiques offrent des opportunités pour de nouvelles idées créatives, mais aussi le potentiel d'ajouter de la complexité au CSS. Pour en tirer le meilleur parti, nous pourrions avoir besoin d'une stratégie sur la façon dont nous écrivons et structurons le CSS avec des propriétés personnalisées.

Les propriétés personnalisées CSS (parfois appelées "variables CSS") sont désormais prises en charge dans tous les navigateurs modernes et les utilisateurs commencent à les utiliser en production. C'est formidable, mais elles sont différentes des variables des préprocesseurs, et j'ai déjà vu de nombreux exemples de personnes les utilisant sans tenir compte des avantages qu'elles offrent.

Les propriétés personnalisées ont un énorme potentiel pour changer la façon dont nous écrivons et structurons le CSS et, dans une moindre mesure, la façon dont nous utilisons JavaScript pour interagir avec les composants de l'interface utilisateur. Je ne vais pas m'attarder sur la syntaxe et leur fonctionnement (pour cela je vous recommande de lire « Il est temps de commencer à utiliser les propriétés personnalisées »). Au lieu de cela, je souhaite approfondir les stratégies permettant de tirer le meilleur parti des propriétés personnalisées CSS.

En quoi sont-elles similaires aux variables dans les préprocesseurs ?

Les propriétés personnalisées ressemblent un peu aux variables des préprocesseurs, mais présentent des différences importantes. La première et la plus évidente des différences est la syntaxe.

Avec SCSS , nous utilisons un symbole dollar pour désigner une variable :

 $smashing-red: #d33a2c;

Dans Less, nous utilisons un symbole @ :

 @smashing-red: #d33a2c;

Les propriétés personnalisées suivent des conventions similaires et utilisent un préfixe -- :

 :root { --smashing-red: #d33a2c; } .smashing-text { color: var(--smashing-red); }

Une différence importante entre les propriétés personnalisées et les variables dans les préprocesseurs est que les propriétés personnalisées ont une syntaxe différente pour attribuer une valeur et récupérer cette valeur. Lors de la récupération de la valeur d'une propriété personnalisée, nous utilisons la fonction var() .

Plus après saut! Continuez à lire ci-dessous ↓

La prochaine différence la plus évidente est dans le nom. Elles sont appelées "propriétés personnalisées" car ce sont en réalité des propriétés CSS. Dans les préprocesseurs, vous pouvez déclarer et utiliser des variables presque n'importe où, y compris en dehors des blocs de déclaration, dans les règles multimédias ou même dans le cadre d'un sélecteur.

 $breakpoint: 800px; $smashing-red: #d33a2c; $smashing-things: ".smashing-text, .cats"; @media screen and (min-width: $breakpoint) { #{$smashing-things} { color: $smashing-red; } }

La plupart des exemples ci-dessus seraient invalides en utilisant des propriétés personnalisées.

Les propriétés personnalisées ont les mêmes règles concernant l'endroit où elles peuvent être utilisées que les propriétés CSS normales. Il vaut bien mieux les considérer comme des propriétés dynamiques que comme des variables. Cela signifie qu'elles ne peuvent être utilisées qu'à l'intérieur d'un bloc de déclaration, ou en d'autres termes, les propriétés personnalisées sont liées à un sélecteur. Il peut s'agir du sélecteur :root ou de tout autre sélecteur valide.

 :root { --smashing-red: #d33a2c; } @media screen and (min-width: 800px) { .smashing-text, .cats { --margin-left: 1em; } }

Vous pouvez récupérer la valeur d'une propriété personnalisée partout où vous utiliseriez autrement une valeur dans une déclaration de propriété. Cela signifie qu'ils peuvent être utilisés comme une valeur unique, dans le cadre d'une déclaration abrégée ou même à l'intérieur d'équations calc() .

 .smashing-text, .cats { color: var(--smashing-red); margin: 0 var(--margin-horizontal); padding: calc(var(--margin-horizontal) / 2) }

Cependant, ils ne peuvent pas être utilisés dans les requêtes multimédias ou les sélecteurs incluant :nth-child() .

Il y a probablement beaucoup plus que vous voulez savoir sur la syntaxe et le fonctionnement des propriétés personnalisées, comme comment utiliser les valeurs de secours et pouvez-vous affecter des variables à d'autres variables (oui), mais cette introduction de base devrait être suffisante pour comprendre le reste de les concepts de cet article. Pour plus d'informations sur les spécificités du fonctionnement des propriétés personnalisées, vous pouvez lire « Il est temps de commencer à utiliser les propriétés personnalisées » écrit par Serg Hospodarets.

Dynamique vs Statique

Mis à part les différences esthétiques, la différence la plus significative entre les variables dans les préprocesseurs et les propriétés personnalisées est la façon dont elles sont définies. Nous pouvons nous référer aux variables comme ayant une portée statique ou dynamique. Les variables des préprocesseurs sont statiques, tandis que les propriétés personnalisées sont dynamiques.

En ce qui concerne CSS, statique signifie que vous pouvez mettre à jour la valeur d'une variable à différents moments du processus de compilation, mais cela ne peut pas modifier la valeur du code qui l'a précédé.

 $background: blue; .blue { background: $background; } $background: red; .red { background: $background; }

résulte en:

 .blue { background: blue; } .red { background: red; }

Une fois que cela est rendu au CSS, les variables ont disparu. Cela signifie que nous pourrions potentiellement lire un fichier .scss et déterminer sa sortie sans rien savoir du HTML, du navigateur ou d'autres entrées. Ce n'est pas le cas avec les propriétés personnalisées.

Les préprocesseurs ont une sorte de "portée de bloc" où les variables peuvent être temporairement modifiées à l'intérieur d'un sélecteur, d'une fonction ou d'un mixin. Cela modifie la valeur d'une variable à l'intérieur du bloc, mais elle reste statique. Ceci est lié au bloc, pas au sélecteur. Dans l'exemple ci-dessous, la variable $background est modifiée à l'intérieur du bloc .example . Il revient à la valeur initiale en dehors du bloc, même si nous utilisons le même sélecteur.

 $background: red; .example { $background: blue; background: $background; } .example { background: $background; }

Cela se traduira par :

 .example { background: blue; } .example { background: red; }

Les propriétés personnalisées fonctionnent différemment. En ce qui concerne les propriétés personnalisées, la portée dynamique signifie qu'elles sont soumises à l'héritage et à la cascade. La propriété est liée à un sélecteur et si la valeur change, cela affecte tous les éléments DOM correspondants comme n'importe quelle autre propriété CSS.

C'est très bien car vous pouvez modifier la valeur d'une propriété personnalisée dans une requête multimédia, avec un pseudo sélecteur tel que hover, ou même avec JavaScript.

 a { --link-color: black; } a:hover, a:focus { --link-color: tomato; } @media screen and (min-width: 600px) { a { --link-color: blue; } } a { color: var(--link-color); }

Nous n'avons pas à modifier l'endroit où la propriété personnalisée est utilisée — nous modifions la valeur de la propriété personnalisée avec CSS. Cela signifie qu'en utilisant la même propriété personnalisée, nous pouvons avoir des valeurs différentes à différents endroits ou contextes sur la même page.

Global vs Local

En plus d'être statiques ou dynamiques, les variables peuvent également être globales ou locales. Si vous écrivez du JavaScript, vous serez familiarisé avec cela. Les variables peuvent soit être appliquées à tout ce qui se trouve dans une application, soit leur portée peut être limitée à des fonctions ou à des blocs de code spécifiques.

CSS est similaire. Nous avons certaines choses qui sont appliquées à l'échelle mondiale et d'autres qui sont plus locales. Les couleurs de la marque, l'espacement vertical et la typographie sont tous des exemples de choses que vous voudrez peut-être appliquer de manière globale et cohérente sur votre site Web ou votre application. Nous avons aussi des choses locales. Par exemple, un composant de bouton peut avoir une variante petite et grande. Vous ne voudriez pas que les tailles de ces boutons soient appliquées à tous les éléments d'entrée ou même à tous les éléments de la page.

C'est quelque chose que nous connaissons bien en CSS. Nous avons développé des systèmes de conception, des conventions de nommage et des bibliothèques JavaScript, le tout pour aider à isoler les composants locaux et les éléments de conception globaux. Les propriétés personnalisées offrent de nouvelles options pour traiter ce vieux problème.

Les propriétés personnalisées CSS sont par défaut limitées localement aux sélecteurs spécifiques auxquels nous les appliquons. Ils sont donc un peu comme des variables locales. Cependant, les propriétés personnalisées sont également héritées, donc dans de nombreuses situations, elles se comportent comme des variables globales, en particulier lorsqu'elles sont appliquées au sélecteur :root . Cela signifie que nous devons réfléchir à la façon de les utiliser.

Tant d'exemples montrent des propriétés personnalisées appliquées à l'élément :root et bien que cela convienne pour une démo, cela peut entraîner une portée globale désordonnée et des problèmes imprévus d'héritage. Heureusement, nous avons déjà appris ces leçons.

Les variables globales ont tendance à être statiques

Il y a quelques petites exceptions, mais d'une manière générale, la plupart des choses globales en CSS sont également statiques.

Les variables globales telles que les couleurs de la marque, la typographie et l'espacement n'ont pas tendance à beaucoup changer d'un composant à l'autre. Lorsqu'ils changent, il s'agit généralement d'un changement de marque mondial ou d'un autre changement important qui se produit rarement sur un produit mature. Il est toujours logique que ces choses soient des variables, elles sont utilisées dans de nombreux endroits et les variables aident à la cohérence. Mais cela n'a pas de sens qu'ils soient dynamiques. La valeur de ces variables ne change pas de manière dynamique.

Pour cette raison, je recommande fortement d'utiliser des préprocesseurs pour les variables globales (statiques). Cela garantit non seulement qu'ils sont toujours statiques, mais cela les dénote visuellement dans le code. Cela peut rendre le CSS beaucoup plus lisible et plus facile à entretenir.

Les variables statiques locales sont correctes (parfois)

Vous pourriez penser qu'étant donné la position ferme sur les variables globales étant statiques, que par réflexion, toutes les variables locales pourraient avoir besoin d'être dynamiques. S'il est vrai que les variables locales ont tendance à être dynamiques, c'est loin d'être aussi fort que la tendance d'une variable globale à être statique.

Les variables localement statiques conviennent parfaitement dans de nombreuses situations. J'utilise des variables de préprocesseurs dans les fichiers de composants principalement pour la commodité du développeur.

Prenons l'exemple classique d'un composant de bouton avec plusieurs variations de taille.

boutons

Mon scss pourrait ressembler à ceci :

 $button-sml: 1em; $button-med: 1.5em; $button-lrg: 2em; .btn { // Visual styles } .btn-sml { font-size: $button-sml; } .btn-med { font-size: $button-med; } .btn-lrg { font-size: $button-lrg; }

De toute évidence, cet exemple aurait plus de sens si j'utilisais les variables plusieurs fois ou si je dérivais les valeurs de marge et de remplissage des variables de taille. Cependant, la possibilité de prototyper rapidement différentes tailles pourrait être une raison suffisante.

Parce que la plupart des variables statiques sont globales, j'aime différencier les variables statiques qui ne sont utilisées qu'à l'intérieur d'un composant. Pour ce faire, vous pouvez préfixer ces variables avec le nom du composant, ou vous pouvez utiliser un autre préfixe tel que nom c-variable-name pour composant ou l-variable-name pour local. Vous pouvez utiliser le préfixe de votre choix ou vous pouvez préfixer des variables globales. Quoi que vous choisissiez, il est utile de faire la différence, en particulier si vous convertissez une base de code existante pour utiliser des propriétés personnalisées.

Quand utiliser les propriétés personnalisées

S'il est acceptable d'utiliser des variables statiques à l'intérieur des composants, quand devrions-nous utiliser des propriétés personnalisées ? La conversion de variables de préprocesseur existantes en propriétés personnalisées n'a généralement aucun sens. Après tout, la raison des propriétés personnalisées est complètement différente. Les propriétés personnalisées ont du sens lorsque nous avons des propriétés CSS qui changent par rapport à une condition dans le DOM — en particulier une condition dynamique telle que :focus , :hover , media queries ou avec JavaScript.

Je soupçonne que nous utiliserons toujours une certaine forme de variables statiques, bien que nous en ayons peut-être besoin à l'avenir, car les propriétés personnalisées offrent de nouvelles façons d'organiser la logique et le code. Jusque-là, je pense que dans la plupart des situations, nous allons travailler avec une combinaison de variables de préprocesseur et de propriétés personnalisées.

Il est utile de savoir que nous pouvons affecter des variables statiques aux propriétés personnalisées. Qu'elles soient globales ou locales, il est logique dans de nombreuses situations de convertir des variables statiques en propriétés personnalisées localement dynamiques.

Remarque : Saviez-vous que $var est une valeur valide pour une propriété personnalisée ? Les versions récentes de Sass le reconnaissent, et nous devons donc interpoler les variables affectées aux propriétés personnalisées, comme ceci : #{$var} . Cela indique à Sass que vous souhaitez afficher la valeur de la variable, plutôt que simplement $var dans la feuille de style. Cela n'est nécessaire que pour des situations telles que les propriétés personnalisées, où les noms de variables peuvent également être un CSS valide.

Si nous prenons l'exemple de bouton ci-dessus et décidons que tous les boutons doivent utiliser la petite variation sur les appareils mobiles, quelle que soit la classe appliquée dans le HTML, il s'agit maintenant d'une situation plus dynamique. Pour cela, nous devons utiliser des propriétés personnalisées.

 $button-sml: 1em; $button-med: 1.5em; $button-lrg: 2em; .btn { --button-size: #{$button-sml}; } @media screen and (min-width: 600px) { .btn-med { --button-size: #{$button-med}; } .btn-lrg { --button-size: #{$button-lrg}; } } .btn { font-size: var(--button-size); }

Ici, je crée une seule propriété personnalisée : --button-size . Cette propriété personnalisée est initialement étendue à tous les éléments de bouton utilisant la classe btn . Je change ensuite la valeur de --button-size au-dessus de 600px pour les classes btn-med et btn-lrg . Enfin, j'applique cette propriété personnalisée à tous les éléments de bouton en un seul endroit.

Ne soyez pas trop malin

La nature dynamique des propriétés personnalisées nous permet de créer des composants intelligents et compliqués.

Avec l'introduction des préprocesseurs, beaucoup d'entre nous ont créé des bibliothèques avec des abstractions intelligentes en utilisant des mixins et des fonctions personnalisées. Dans des cas limités, des exemples comme celui-ci sont encore utiles aujourd'hui, mais pour la plupart, plus je travaille longtemps avec des préprocesseurs, moins j'utilise de fonctionnalités. Aujourd'hui, j'utilise presque exclusivement des préprocesseurs pour les variables statiques.

Les propriétés personnalisées ne seront pas (et ne devraient pas) être à l'abri de ce type d'expérimentation, et j'ai hâte de voir de nombreux exemples intelligents. Mais à long terme, un code lisible et maintenable l'emportera toujours sur les abstractions astucieuses (du moins en production).

J'ai lu récemment un excellent article sur ce sujet sur le Free Code Camp Medium. Il a été écrit par Bill Sourour et s'intitule « Don't Do It At Runtime. Faites-le au moment de la conception. Plutôt que de paraphraser ses arguments, je vous laisse le lire.

Une différence clé entre les variables de préprocesseur et les propriétés personnalisées est que les propriétés personnalisées fonctionnent au moment de l'exécution. Cela signifie que des choses qui auraient pu être acceptables à la limite, en termes de complexité, avec des préprocesseurs pourraient ne pas être une bonne idée avec des propriétés personnalisées.

Un exemple qui m'a illustré cela récemment était celui-ci :

 :root { --font-scale: 1.2; --font-size-1: calc(var(--font-scale) * var(--font-size-2)); --font-size-2: calc(var(--font-scale) * var(--font-size-3)); --font-size-3: calc(var(--font-scale) * var(--font-size-4)); --font-size-4: 1rem; }

Cela génère une échelle modulaire. Une échelle modulaire est une série de nombres liés les uns aux autres à l'aide d'un rapport. Ils sont souvent utilisés dans la conception et le développement Web pour définir les tailles de police ou l'espacement.

Dans cet exemple, chaque propriété personnalisée est déterminée à l'aide de calc() , en prenant la valeur de la propriété personnalisée précédente et en la multipliant par le ratio. Ce faisant, nous pouvons obtenir le nombre suivant dans l'échelle.

Cela signifie que les ratios sont calculés au moment de l'exécution et que vous pouvez les modifier en mettant à jour uniquement la valeur de la propriété --font-scale . Par exemple:

 @media screen and (min-width: 800px) { :root { --font-scale: 1.33; } }

C'est astucieux, concis et beaucoup plus rapide que de recalculer toutes les valeurs si vous souhaitez modifier l'échelle. C'est aussi quelque chose que je ne ferais pas dans le code de production.

Bien que l'exemple ci-dessus soit utile pour le prototypage, en production, je préférerais de loin voir quelque chose comme ceci :

 :root { --font-size-1: 1.728rem; --font-size-2: 1.44rem; --font-size-3: 1.2em; --font-size-4: 1em; } @media screen and (min-width: 800px) { :root { --font-size-1: 2.369rem; --font-size-2: 1.777rem; --font-size-3: 1.333rem; --font-size-4: 1rem; } }

Semblable à l'exemple de l'article de Bill, je trouve utile de voir quelles sont les valeurs réelles. Nous lisons le code beaucoup plus de fois que nous ne l'écrivons et les valeurs globales telles que les échelles de police changent rarement en production.

L'exemple ci-dessus n'est toujours pas parfait. Cela enfreint la règle antérieure selon laquelle les valeurs globales doivent être static . Je préférerais de loin utiliser des variables de préprocesseur et les convertir en propriétés personnalisées localement dynamiques à l'aide des techniques présentées précédemment.

Il est également important d'éviter les situations où nous passons de l'utilisation d'une propriété personnalisée à une propriété personnalisée différente. Cela peut se produire lorsque nous nommons des propriétés comme celle-ci.

Changer la valeur pas la variable

Changer la valeur et non la variable est l'une des stratégies les plus importantes pour utiliser efficacement les propriétés personnalisées.

En règle générale, vous ne devez jamais modifier la propriété personnalisée utilisée dans un but précis. C'est facile à faire parce que c'est exactement comme ça qu'on fait avec les préprocesseurs, mais ça n'a pas beaucoup de sens avec les propriétés personnalisées.

Dans cet exemple, nous avons deux propriétés personnalisées qui sont utilisées sur un exemple de composant. Je passe de la valeur de --font-size-small à --font-size-large en fonction de la taille de l'écran.

 :root { --font-size-small: 1.2em; --font-size-large: 2em; } .example { font-size: var(--font-size-small); } @media screen and (min-width: 800px) { .example { font-size: var(--font-size-large); } }

Une meilleure façon de procéder serait de définir une seule propriété personnalisée étendue au composant. Ensuite, à l'aide d'une requête multimédia ou de tout autre sélecteur, modifiez sa valeur.

 .example { --example-font-size: 1.2em; } @media screen and (min-width: 800px) { .example { --example-font-size: 2em; } }

Enfin, en un seul endroit, j'utilise la valeur de cette propriété personnalisée :

 .example { font-size: var(--example-font-size); }

Dans cet exemple et d'autres avant lui, les requêtes média n'ont été utilisées que pour modifier la valeur des propriétés personnalisées. Vous remarquerez peut-être également qu'il n'y a qu'un seul endroit où l'instruction var() est utilisée et que les propriétés CSS habituelles sont mises à jour.

Cette séparation entre les déclarations de variables et les déclarations de propriétés est intentionnelle. Il y a plusieurs raisons à cela, mais les avantages sont plus évidents lorsque l'on pense à la conception réactive.

Conception réactive avec propriétés personnalisées

L'une des difficultés de la conception réactive lorsqu'elle s'appuie fortement sur les requêtes multimédias est que, quelle que soit la façon dont vous organisez votre CSS, les styles liés à un composant particulier sont fragmentés dans la feuille de style.

Il peut être très difficile de savoir quelles propriétés CSS vont changer. Néanmoins, les propriétés personnalisées CSS peuvent nous aider à organiser une partie de la logique liée à la conception réactive et à faciliter considérablement le travail avec les requêtes multimédias.

Si ça change, c'est une variable

Les propriétés qui changent à l'aide des requêtes multimédias sont intrinsèquement dynamiques et les propriétés personnalisées permettent d'exprimer des valeurs dynamiques dans CSS. Cela signifie que si vous utilisez une requête multimédia pour modifier une propriété CSS, vous devez placer cette valeur dans une propriété personnalisée.

Vous pouvez ensuite déplacer cela, ainsi que toutes les règles multimédias, les états de survol ou tous les sélecteurs dynamiques qui définissent la façon dont la valeur change, en haut du document.

Séparer la logique de la conception

Lorsqu'elle est effectuée correctement, la séparation de la logique et de la conception signifie que les requêtes multimédia ne sont utilisées que pour modifier la valeur des propriétés personnalisées . Cela signifie que toute la logique liée à la conception réactive doit être en haut du document, et partout où nous voyons une instruction var() dans notre CSS, nous savons immédiatement que cette propriété change. Avec les méthodes traditionnelles d'écriture CSS, il n'y avait aucun moyen de le savoir en un coup d'œil.

Beaucoup d'entre nous sont devenus très bons pour lire et interpréter le CSS en un coup d'œil tout en suivant dans notre tête quelles propriétés ont changé dans différentes situations. J'en ai marre et je ne veux plus faire ça ! Les propriétés personnalisées fournissent désormais un lien entre la logique et son implémentation, nous n'avons donc pas besoin de suivre cela, et c'est incroyablement utile !

Le pli logique

L'idée de déclarer des variables en haut d'un document ou d'une fonction n'est pas une idée nouvelle. C'est quelque chose que nous faisons dans la plupart des langages, et c'est maintenant aussi quelque chose que nous pouvons faire en CSS. L'écriture CSS de cette manière crée une distinction visuelle claire entre CSS en haut du document et en dessous. J'ai besoin d'un moyen de différencier ces sections quand j'en parle et l'idée d'un "pli logique" est une métaphore que j'ai commencé à utiliser.
Au-dessus du pli contient toutes les variables de préprocesseur et les propriétés personnalisées. Cela inclut toutes les différentes valeurs qu'une propriété personnalisée peut avoir. Il devrait être facile de suivre comment une propriété personnalisée change.

Le CSS sous le pli est simple, hautement déclaratif et facile à lire. Cela ressemble à du CSS avant les requêtes multimédias et autres complexités nécessaires du CSS moderne.

Jetez un œil à un exemple très simple d'un système de grille flexbox à six colonnes :

 .row { --row-display: block; } @media screen and (min-width: 600px) { .row { --row-display: flex; } }

La propriété personnalisée --row-display est initialement définie sur block . Au-dessus de 600px, le mode d'affichage est défini sur flex.

Ci-dessous, le pli pourrait ressembler à ceci :

 .row { display: var(--row-display); flex-direction: row; flex-wrap: nowrap; } .col-1, .col-2, .col-3, .col-4, .col-5, .col-6 { flex-grow: 0; flex-shrink: 0; } .col-1 { flex-basis: 16.66%; } .col-2 { flex-basis: 33.33%; } .col-3 { flex-basis: 50%; } .col-4 { flex-basis: 66.66%; } .col-5 { flex-basis: 83.33%; } .col-6 { flex-basis: 100%; }

Nous savons immédiatement que --row-display est une valeur qui change. Initialement, ce sera block , donc les valeurs flex seront ignorées.

Cet exemple est assez simple, mais si nous l'avons étendu pour inclure une colonne de largeur flexible qui remplit l'espace restant, il est probable que les valeurs flex-grow , flex-shrink et flex-basis devront être converties en propriétés personnalisées. Vous pouvez essayer ceci ou jeter un œil à un exemple plus détaillé ici.

Propriétés personnalisées pour la thématisation

J'ai principalement plaidé contre l'utilisation de propriétés personnalisées pour les variables dynamiques globales et j'espère que l'attachement de propriétés personnalisées au sélecteur :root est dans de nombreux cas considéré comme nuisible. Mais chaque règle a une exception, et pour les propriétés personnalisées, c'est la thématisation.

L'utilisation limitée des propriétés personnalisées globales peut rendre la création de thèmes beaucoup plus facile.

La thématisation fait généralement référence à la possibilité pour les utilisateurs de personnaliser l'interface utilisateur d'une manière ou d'une autre. Cela pourrait être quelque chose comme changer les couleurs sur une page de profil. Ou cela pourrait être quelque chose de plus localisé. Par exemple, vous pouvez choisir la couleur d'une note dans l'application Google Keep.

Application Google Keep

La thématisation implique généralement la compilation d'une feuille de style distincte pour remplacer une valeur par défaut par les préférences de l'utilisateur, ou la compilation d'une feuille de style différente pour chaque utilisateur. Les deux peuvent être difficiles et avoir un impact sur les performances.

Avec les propriétés personnalisées, nous n'avons pas besoin de compiler une feuille de style différente ; nous n'avons qu'à mettre à jour la valeur des propriétés en fonction des préférences de l'utilisateur. Comme ce sont des valeurs héritées, si nous le faisons sur l'élément racine, elles peuvent être utilisées n'importe où dans notre application.

Capitaliser les propriétés dynamiques globales

Les propriétés personnalisées sont sensibles à la casse et comme la plupart des propriétés personnalisées seront locales, si vous utilisez des propriétés dynamiques globales, il peut être judicieux de les mettre en majuscules.

 :root { --THEME-COLOR: var(--user-theme-color, #d33a2c); }

La capitalisation des variables signifie souvent des constantes globales. Pour nous, cela va signifier que la propriété est définie ailleurs dans l'application et que nous ne devrions probablement pas la modifier localement.

Évitez de définir directement les propriétés dynamiques globales

Les propriétés personnalisées acceptent une valeur de secours. Il peut être utile d'éviter d'écraser directement la valeur d'une propriété personnalisée globale et de conserver les valeurs utilisateur séparées. Nous pouvons utiliser la valeur de repli pour ce faire.

L'exemple ci-dessus définit la valeur de --THEME-COLOR sur la valeur de --user-theme-color si elle existe. Si --user-theme-color n'est pas défini, la valeur de #d33a2c sera utilisée. De cette façon, nous n'avons pas besoin de fournir une solution de secours à chaque fois que nous utilisons --THEME-COLOR .

Dans l'exemple ci-dessous, vous pouvez vous attendre à ce que l'arrière-plan soit défini sur green . Cependant, la valeur de --user-theme-color n'a pas été définie sur l'élément racine, donc la valeur de --THEME-COLOR n'a pas changé.

 :root { --THEME-COLOR: var(--user-theme-color, #d33a2c); } body { --user-theme-color: green; background: var(--THEME-COLOR); }

La définition indirecte de propriétés dynamiques globales comme celle-ci les empêche d'être écrasées localement et garantit que les paramètres utilisateur sont toujours hérités de l'élément racine. Il s'agit d'une convention utile pour protéger les valeurs de votre thème et éviter un héritage involontaire.

Si nous voulons exposer des propriétés spécifiques à l'héritage, nous pouvons remplacer le sélecteur :root par un sélecteur * :

 * { --THEME-COLOR: var(--user-theme-color, #d33a2c); } body { --user-theme-color: green; background: var(--THEME-COLOR); }

Maintenant, la valeur de --THEME-COLOR est recalculée pour chaque élément et donc la valeur locale de --user-theme-color peut être utilisée. En d'autres termes, la couleur d'arrière-plan dans cet exemple sera le green .

Vous pouvez voir des exemples plus détaillés de ce modèle dans la section sur la manipulation des couleurs avec des propriétés personnalisées.

Mise à jour des propriétés personnalisées avec JavaScript

Si vous souhaitez définir des propriétés personnalisées à l'aide de JavaScript, il existe une API assez simple et elle ressemble à ceci :

 const elm = document.documentElement; elm.style.setProperty('--USER-THEME-COLOR', 'tomato');

Ici, je définis la valeur de --USER-THEME-COLOR sur l'élément document, ou en d'autres termes, l'élément :root où il sera hérité par tous les éléments.

Ce n'est pas une nouvelle API ; c'est la même méthode JavaScript pour mettre à jour les styles sur un élément. Ce sont des styles en ligne, ils auront donc une spécificité plus élevée que le CSS ordinaire.

Cela signifie qu'il est facile d'appliquer des personnalisations locales :

 .note { --note-color: #eaeaea; } .note { background: var(--note-color); }

Ici, j'ai défini une valeur par défaut pour --note-color et l'ai étendue au composant .note . Je garde la déclaration de variable séparée de la déclaration de propriété, même dans cet exemple simple.

 const elm = document.querySelector('#note-uid'); elm.style.setProperty('--note-color', 'yellow');

Je cible ensuite une instance spécifique d'un élément .note et modifie la valeur de la propriété personnalisée --note-color pour cet élément uniquement. Cela aura maintenant une spécificité plus élevée que la valeur par défaut.

Vous pouvez voir comment cela fonctionne avec cet exemple en utilisant React. Ces préférences utilisateur peuvent être enregistrées dans un stockage local ou, peut-être dans le cas d'une application plus importante, dans une base de données.

Manipulation des couleurs avec des propriétés personnalisées

En plus des valeurs hexadécimales et des couleurs nommées, CSS a des fonctions de couleurs telles que rgb() et hsl() . Ceux-ci nous permettent de spécifier les composants individuels d'une couleur tels que la teinte ou la luminosité. Les propriétés personnalisées peuvent être utilisées conjointement avec les fonctions de couleur.

 :root { --hue: 25; } body { background: hsl(var(--hue), 80%, 50%); }

C'est utile, mais certaines des fonctionnalités les plus largement utilisées des préprocesseurs sont des fonctions de couleur avancées qui nous permettent de manipuler la couleur à l'aide de fonctions telles que l'éclaircissement, l'assombrissement ou la désaturation :

 darken($base-color, 10%); lighten($base-color, 10%); desaturate($base-color, 20%);

Il serait utile d'avoir certaines de ces fonctionnalités dans les navigateurs. Ils arrivent, mais jusqu'à ce que nous ayons des fonctions de modification de couleur natives dans CSS, les propriétés personnalisées pourraient combler une partie de cette lacune.

Nous avons vu que les propriétés personnalisées peuvent être utilisées dans des fonctions de couleur existantes telles que rgb() et hsl() , mais elles peuvent également être utilisées dans calc() . Cela signifie que nous pouvons convertir un nombre réel en pourcentage en le multipliant, par exemple calc(50 * 1%) = 50% .

 :root { --lightness: 50; } body { background: hsl(25, 80%, calc(var(--lightness) * 1%)); }

La raison pour laquelle nous voulons stocker la valeur de luminosité sous forme de nombre réel est que nous pouvons la manipuler avec calc avant de la convertir en pourcentage. Par exemple, si je veux assombrir une couleur de 20% , je peux multiplier sa luminosité par 0.8 . Nous pouvons rendre cela un peu plus facile à lire en séparant le calcul de la luminosité dans une propriété personnalisée à portée locale :

 :root { --lightness: 50; } body { --lightness: calc(var(--lightness * 0.8)); background: hsl(25, 80%, calc(var(--lightness) * 1%)); }

Nous pourrions même extraire davantage de calculs et créer quelque chose comme des fonctions de modification de couleur dans CSS en utilisant des propriétés personnalisées. Cet exemple est probablement trop complexe pour la plupart des cas pratiques de thématisation, mais il démontre toute la puissance des propriétés personnalisées dynamiques.

Simplifier la thématisation

L'un des avantages de l'utilisation de propriétés personnalisées est la possibilité de simplifier la thématisation. L'application n'a pas besoin de savoir comment les propriétés personnalisées sont utilisées. Au lieu de cela, nous utilisons JavaScript ou du code côté serveur pour définir la valeur des propriétés personnalisées. La façon dont ces valeurs sont utilisées est déterminée par les feuilles de style.

Cela signifie une fois de plus que nous sommes capables de séparer la logique de la conception. Si vous disposez d'une équipe de conception technique, les auteurs peuvent mettre à jour les feuilles de style et décider comment appliquer des propriétés personnalisées sans modifier une seule ligne de code JavaScript ou backend.

Les propriétés personnalisées permettent également de déplacer une partie de la complexité de la thématisation dans le CSS et cette complexité peut avoir un impact négatif sur la maintenabilité de votre CSS, alors n'oubliez pas de le garder simple dans la mesure du possible.

Utiliser les propriétés personnalisées aujourd'hui

Même si vous prenez en charge IE10 et 11, vous pouvez commencer à utiliser les propriétés personnalisées dès aujourd'hui. La plupart des exemples de cet article concernent la manière dont nous écrivons et structurons le CSS. Les avantages sont importants en termes de maintenabilité, cependant, la plupart des exemples ne font que réduire ce qui pourrait autrement être fait avec un code plus complexe.

J'utilise un outil appelé postcss-css-variables pour convertir la plupart des fonctionnalités des propriétés personnalisées en une représentation statique du même code. D'autres outils similaires ignorent les propriétés personnalisées dans les requêtes multimédias ou les sélecteurs complexes, traitant les propriétés personnalisées comme des variables de préprocesseur.

Ce que ces outils ne peuvent pas faire, c'est émuler les fonctionnalités d'exécution des propriétés personnalisées. Cela signifie qu'il n'y a pas de fonctionnalités dynamiques telles que la thématisation ou la modification de propriétés avec JavaScript. Cela peut convenir dans de nombreuses situations. Selon la situation, la personnalisation de l'interface utilisateur peut être considérée comme une amélioration progressive et le thème par défaut peut être parfaitement acceptable pour les navigateurs plus anciens.

Chargement de la feuille de style correcte

Il existe de nombreuses façons d'utiliser postCSS. J'utilise un processus gulp pour compiler des feuilles de style distinctes pour les navigateurs plus récents et plus anciens. Une version simplifiée de ma tâche gulp ressemble à ceci :

 import gulp from "gulp"; import sass from "gulp-sass"; import postcss from "gulp-postcss"; import rename from "gulp-rename"; import cssvariables from "postcss-css-variables"; import autoprefixer from "autoprefixer"; import cssnano from "cssnano"; gulp.task("css-no-vars", () => gulp .src("./src/css/*.scss") .pipe(sass().on("error", sass.logError)) .pipe(postcss([cssvariables(), cssnano()])) .pipe(rename({ extname: ".no-vars.css" })) .pipe(gulp.dest("./dist/css")) ); gulp.task("css", () => gulp .src("./src/css/*.scss") .pipe(sass().on("error", sass.logError)) .pipe(postcss([cssnano()])) .pipe(rename({ extname: ".css" })) .pipe(gulp.dest("./dist/css")) );

Il en résulte deux fichiers CSS : un standard avec des propriétés personnalisées ( styles.css ) et un pour les anciens navigateurs ( styles.no-vars.css ). Je veux qu'IE10 et 11 soient servis styles.no-vars.css et d'autres navigateurs pour obtenir le fichier CSS normal.

Normalement, je préconiserais d'utiliser des requêtes de fonctionnalités, mais IE11 ne prend pas en charge les requêtes de fonctionnalités et nous avons tellement utilisé les propriétés personnalisées que servir une feuille de style différente a du sens dans ce cas.

Servir intelligemment une feuille de style différente et éviter un flash de contenu sans style n'est pas une tâche simple. Si vous n'avez pas besoin des fonctionnalités dynamiques des propriétés personnalisées, vous pouvez envisager de servir all browser styles.no-vars.css et d'utiliser les propriétés personnalisées simplement comme un outil de développement.

Si vous souhaitez profiter pleinement de toutes les fonctionnalités dynamiques des propriétés personnalisées, je vous suggère d'utiliser une technique CSS critique. En suivant ces techniques, la feuille de style principale est chargée de manière asynchrone tandis que le CSS critique est rendu en ligne. L'en-tête de votre page pourrait ressembler à ceci :

 <head> <style> /* inlined critical CSS */ </style> <script> loadCSS('non-critical.css'); </script> </head>

Nous pouvons étendre cela pour charger styles.css ou styles.no-vars.css selon que le navigateur prend en charge les propriétés personnalisées. Nous pouvons détecter un support comme celui-ci :

 if ( window.CSS && CSS.supports('color', 'var(--test)') ) { loadCSS('styles.css'); } else { loadCSS('styles.no-vars.css'); }

Conclusion

Si vous avez du mal à organiser efficacement le CSS, si vous rencontrez des difficultés avec les composants réactifs, si vous souhaitez implémenter une thématisation côté client ou si vous souhaitez simplement démarrer du bon pied avec des propriétés personnalisées, ce guide devrait vous dire tout ce que vous devez savoir.

Cela revient à comprendre la différence entre les variables dynamiques et statiques en CSS ainsi que quelques règles simples :

  1. Séparez la logique de la conception ;
  2. Si une propriété CSS change, envisagez d'utiliser une propriété personnalisée ;
  3. Modifiez la valeur des propriétés personnalisées, et non la propriété personnalisée utilisée ;
  4. Les variables globales sont généralement statiques.

If you follow these conventions, you will find that working with custom properties is a whole lot easier than you think. This might even change how you approach CSS in general.

Lectures complémentaires

  • “It's Time To Start Using Custom Properties,” Serg Hospodarets
    A general introduction to the syntax and the features of custom properties.
  • “Pragmatic, Practical, And Progressive Theming With Custom Properties,” Harry Roberts
    More useful information on theming.
  • Custom Properties Collection, Mike Riethmuller on CodePen
    A number of different examples you can experiment with.