Cascade et spécificité

CSS

On joue ?

quelle est ma couleur ?

spécificité des sélecteurs CSS

plus c'est haut, plus c'est violent

opérateurs (* > + ~ espace)
éléments (+ pseudo-éléments)
.class (+ pseudo-classes) + attribut
#id
style=""
!important
:not()
:where()
:has()

Pas d'ID en CSS !

Project Wallace

!important + specificity + nesting

Fil rouge

CSS a (enfin) résolu les problèmes de spécificité des sélecteurs !

.navigation button { color: salmon; }
.navigation li .button-primary { color: orange; }
.navigation button.button-primary { color: olive; }
.navigation .button-primary { color: chocolate; }
body nav li button.button-primary { color: tomato; }

styles-existants.css

Brief client : "je veux un bouton rose !"

Les joies de la spécificité CSS…

.navigation .button-primary { color: pink; }

correctif.css

Pas de chance : le bouton est olive et restera olive 🤷

Contournements rapides

Me dis pas que t'as jamais fait ça

Ces techniques compliquent la maintenance des styles 🤷

.navigation button { color: salmon; }
.navigation li .button-primary { color: orange; }
.navigation button.button-primary { color: olive; }
.navigation .button-primary { color: chocolate; }
body nav li button.button-primary { color: tomato; }

styles-existants.css

correctif.css

.navigation .button-primary { color: pink !important; }
.navigation button.button-primary { color: pink; }

bootstrap.min.css (1043x !important)

Les vraies solutions modernes proposées par CSS

Cascade Layers

Définit l'ordre des couches de cascade

.navigation button.button-primary { color: olive; }

styles-existants.css

.navigation .button-primary { color: pink; }

correctif.css

C'est moi qui gagne !

.button-primary { color: pink; }

correctif.css

Les styles hors-layer sont prioritaires !

(= c'est moi qui gagne)

@layer legacy {
  .navigation button.button-primary { color: olive; }
}

styles-existants.css

Je place ces styles dans une couche nommée "legacy"

Cascade Layers

Idéal pour compartimenter les fichiers du projet

fichier-qui-regroupe-tous-les-autres.css

@layer legacy, styles;

@import "styles-existants.css" layer(legacy);
@import "correctifs.css" layer(styles);

L'ordre de cette liste définit la priorité de chaque layer

Le layer "styles" est prioritaire sur le layer "legacy"

.navigation button.button-primary { color: olive; }

styles-existants.css

.button-primary { color: pink; }

correctif.css

C'est moi qui gagne !

Cascade Layers

On range chaque fichier à sa place !

fichier-qui-regroupe-tous-les-autres.css

@layer config, base, components, utilities;

/* Config */
@import "reset.css" layer(config);
@import "theme.css" layer(config);

/* Base */
@import "styles.css" layer(base);

/* Components */
@import "accordion.css" layer(components);
@import "card.css" layer(components);
@import "navigation.css" layer(components);

/* Utilities */
@import "utilities.css" layer(utilities);

Ressource @layer

@scope

Isole les styles au sein d'un élément

@scope (.navigation) {
  button { color: hotpink; }
}

@scope

Aucun style ne sort du scope !

Cible les button uniquement dans .navigation

@scope (.navigation) {
  :scope {
    background: var(--surface);
  }
  button {
    background: pink;
    
    &:hover, &:focus-visible {
      background: hotpink;
    }
  }
}

Cible .navigation elle-même

Nesting évidemment accepté

@scope

facilite le nommage et évite les conflits !

.card__title

.hero__title--alternate

.media-content

.component-footer

.form-content-secondary

.button-grid-primary

.title

.media

.header

.footer

.content

.desc

@scope (.navigation) {
  .button { ...}
  .link { ... }
}

Au sein d'un scope, on peut se contenter de noms très simples

(au revoir BEM !)

Ressource @scope

:where

Réduit la spécificité d'un sélecteur

#navigation button.button-primary { color: olive; }

Spécificité de 1, 1, 1

(1x ID, 1x Class, 1x Element)

La pseudo-classe :where (et ce qu'elle contient) a une spécificité de 0

#navigation button:where(.button-primary) { color: olive; }

Spécificité de 1, 0, 1

(1x ID, 1x Element)

#navigation :where(button.button-primary) { color: olive; }

Spécificité de 1, 0, 0

(1x ID)

:where(#navigation button.button-primary) { color: olive; }

Spécificité de 0, 0, 0

@layer Vs @scope Vs :where

  1. @layer et @scope sont complémentaires,
  2. Utiliser @layer pour la priorité des fichiers CSS,
  3. Utiliser @scope pour isoler au sein d'un composant,
  4. Au cas par cas, si nécessaire, abaisser la spécificité d'un sélecteur avec :where

app.css

@layer config, base, components, utilities;
...

/* Components */
@import "navigation.css" layer(components);
@scope (.navigation) {
  /* Vos styles ici */
}

navigation.css

Les scopes doivent idéalement résider dans le layer "components"

Exercice !

Cascade