Kodokon kodokon.com

Cascade layers (@layer) et @scope

Maîtrisez les deux nouveaux critères de la cascade - les couches et la proximité de portée - pour architecturer vos styles sans guerre de spécificité.

10 min · 3 questions

Ouvrir cette leçon dans Kodokon

La cascade départage les déclarations en conflit selon des critères ordonnés : origine et importance, puis contexte, puis styles d'attribut style, puis couches (@layer), puis spécificité, puis ordre d'apparition. Point capital : les couches sont évaluées avant la spécificité. Une règle .button dans une couche prioritaire bat donc #nav div.menu a.button dans une couche moins prioritaire - la spécificité (1,2,2) ne pèse rien face à l'ordre des couches. C'est ce qui rend @layer si puissant pour architecturer un design system : la priorité est déclarée une fois, structurellement, au lieu d'être gagnée sélecteur par sélecteur.

CSS
@layer reset, base, components, utilities;

@layer components {
  .button { background: rebeccapurple; }
}

@layer base {
  button { background: gray; }
}
La déclaration anticipée fixe l'ordre : components bat base, quel que soit l'ordre des blocs dans le fichier.

Deux subtilités de spécification à connaître absolument. Un : les styles hors de toute couche gagnent contre les styles en couche (pour les déclarations normales) - ils forment une couche implicite placée en dernier. Vos surcharges locales battent donc toujours la bibliothèque mise en couche. Deux : avec !important, l'ordre s'inverse intégralement. Les premières couches déclarées l'emportent, et un !important en couche bat un !important hors couche. Une déclaration !important dans votre couche reset devient quasiment imbattable.

CSS
@layer base, theme;

@layer base {
  a { color: black !important; }
}

@layer theme {
  a { color: purple !important; }
}
Avec !important, l'ordre s'inverse : black gagne car base est déclarée en premier.

@scope borne un ensemble de règles entre une racine de portée et une éventuelle limite basse : @scope (.card) to (.card__content) cible les éléments descendants de .card mais hors de .card__content - le fameux donut scope, idéal pour styler le cadre d'un composant sans fuir dans son contenu injecté. À l'intérieur, :scope désigne la racine elle-même. Et @scope introduit un critère de cascade inédit : la proximité. À spécificité égale, la règle dont la racine de portée est la plus proche de l'élément gagne - ce que ni BEM ni les combinateurs ne savaient exprimer.

CSS
@scope (.card) to (.card__content) {
  :scope {
    padding: 1rem;
    border: 1px solid oklch(0.85 0.02 260);
  }
  img { border-radius: 8px; }
}
Les img du cadre de .card sont ciblées ; celles injectées dans .card__content sont hors de portée.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Avec @layer a, b; puis deux déclarations normales de même spécificité, l'une dans a et l'autre dans b, laquelle s'applique ?
    • Celle de la couche a, déclarée en premier
    • Celle de la couche b, car la dernière couche déclarée l'emporte
    • Celle qui apparaît le plus tard dans le fichier, couches ignorées
  2. Une déclaration normale hors de toute couche affronte une déclaration normale placée en couche. Résultat ?
    • La déclaration en couche gagne toujours
    • La déclaration hors couche gagne
    • La spécificité des sélecteurs départage
    • Le navigateur signale un conflit
  3. Dans @scope (.card) to (.card__content), quels éléments les règles internes peuvent-elles cibler ?
    • Tous les descendants de .card sans exception
    • Les descendants de .card situés hors de .card__content (donut scope)
    • Uniquement .card__content et ses enfants