Kodokon kodokon.com

Cascade Layers (@layer) und @scope

Beherrsche die zwei neuen Kaskadenkriterien - Layers und Scope-Nähe -, um deine Stile ohne Spezifitäts-Kriege zu strukturieren.

10 Min. · 3 Fragen

Diese Lektion in Kodokon öffnen

Die Kaskade löst widersprüchliche Deklarationen anhand geordneter Kriterien auf: Ursprung und Wichtigkeit, dann Kontext, dann Stile aus dem style-Attribut, dann Layers (@layer), dann Spezifität, dann Reihenfolge des Auftretens. Der entscheidende Punkt: Layers werden vor der Spezifität ausgewertet. Eine .button-Regel in einem Layer mit höherer Priorität schlägt daher #nav div.menu a.button in einem Layer mit niedrigerer Priorität - ihre Spezifität von (1,2,2) zählt nichts gegen die Layer-Reihenfolge. Genau das macht @layer so mächtig, um ein Designsystem zu strukturieren: Die Priorität wird einmal, strukturell festgelegt, statt Selektor für Selektor erkämpft zu werden.

CSS
@layer reset, base, components, utilities;

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

@layer base {
  button { background: gray; }
}
Die vorab platzierte Deklaration legt die Reihenfolge fest: components schlägt base, unabhängig von der Reihenfolge der Blöcke in der Datei.

Zwei Feinheiten der Spezifikation, die du unbedingt kennen musst. Erstens: Stile außerhalb jedes Layers gewinnen gegen Stile in Layers (bei normalen Deklarationen) - sie bilden einen impliziten Layer, der zuletzt einsortiert wird. Deine lokalen Overrides schlagen daher immer die in Layers verpackte Bibliothek. Zweitens: Mit !important kehrt sich die Reihenfolge vollständig um. Die zuerst deklarierten Layers gewinnen, und ein !important innerhalb eines Layers schlägt ein !important außerhalb jedes Layers. Eine !important-Deklaration in deinem reset-Layer wird nahezu unschlagbar.

CSS
@layer base, theme;

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

@layer theme {
  a { color: purple !important; }
}
Mit !important kehrt sich die Reihenfolge um: black gewinnt, weil base zuerst deklariert wird.

@scope begrenzt eine Menge von Regeln zwischen einer Scoping-Root und einer optionalen unteren Grenze: @scope (.card) to (.card__content) zielt auf Elemente, die Nachfahren von .card, aber außerhalb von .card__content sind - der berühmte Donut-Scope, ideal, um den Rahmen einer Komponente zu gestalten, ohne in ihren eingefügten Inhalt überzulaufen. Innerhalb verweist :scope auf die Root selbst. Und @scope führt ein brandneues Kaskadenkriterium ein: Nähe. Bei gleicher Spezifität gewinnt die Regel, deren Scoping-Root dem Element am nächsten ist - etwas, das weder BEM noch Kombinatoren je ausdrücken konnten.

CSS
@scope (.card) to (.card__content) {
  :scope {
    padding: 1rem;
    border: 1px solid oklch(0.85 0.02 260);
  }
  img { border-radius: 8px; }
}
Die img-Elemente im Rahmen von .card werden erfasst; jene, die in .card__content eingefügt werden, sind außerhalb des Scopes.

Wissenscheck

Stelle sicher, dass du die wichtigsten Punkte dieser Lektion behalten hast.

  1. Vervollständige die vorgezogene Deklaration, damit die Ebene components die Ebene base übertrumpft.
    @layer ___;
    
    @layer components {
      .button { background: purple; }
    }
    
    @layer base {
      .button { background: gray; }
    }
    • base, components
    • components, base
    • base components
  2. Eine normale Deklaration außerhalb jedes Layers trifft auf eine normale Deklaration in einem Layer. Ergebnis?
    • Die Deklaration im Layer gewinnt immer
    • Die Deklaration außerhalb von Layers gewinnt
    • Die Spezifität des Selektors entscheidet
    • Der Browser meldet einen Konflikt
  3. In @scope (.card) to (.card__content) - welche Elemente können die inneren Regeln erfassen?
    • Alle Nachfahren von .card ohne Ausnahme
    • Die Nachfahren von .card, die außerhalb von .card__content liegen (Donut-Scope)
    • Nur .card__content und seine Kinder