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. Bei @layer a, b; und zwei normalen Deklarationen gleicher Spezifität, eine in a und eine in b - welche gilt?
    • Die in Layer a, das zuerst deklariert wurde
    • Die in Layer b, weil der zuletzt deklarierte Layer gewinnt
    • Die, die zuletzt in der Datei erscheint, Layers ignoriert
  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