# CSS @layer in der Praxis: Kaskadenebenen statt Spezifitätskrieg

> Cascade Layers von Grund auf: Syntax, Kaskaden-Reihenfolge, Frameworks wie Bootstrap und Tailwind im eigenen Layer, Shadow DOM und Design-Tokens.

Source: https://www.jpkc.com/db/blog/css-cascade-layers/

Es gibt eine Zeile CSS, die fast jeder schon geschrieben hat und danach bereut:

```css
.sidebar .widget ul li a.link { color: #fff !important; }
```

Diese Zeile ist kein Styling. Sie ist eine Kapitulation. Jemand wollte eine Farbe ändern, ist an einem Selektor gescheitert, hat einen weiteren Klassennamen davorgesetzt, ist wieder gescheitert, und am Ende kam das `!important` dazu. Das Ergebnis funktioniert — bis jemand anderes dasselbe Spiel eine Ebene höher spielt.

Genau dafür gibt es seit März 2022 in allen Browser-Engines eine Lösung: **Cascade Layers**, die At-Rule `@layer`. At-Rules sind CSS-Anweisungen, die mit einem `@` beginnen — `@media`, `@import`, `@supports`; sie enden entweder mit Semikolon oder umschließen einen eigenen Block. Diese hier fügt der CSS-Kaskade eine Ebene hinzu, die *über* der Spezifität liegt. (Die Kaskade ist der Algorithmus, mit dem der Browser bei konkurrierenden Angaben für dieselbe Eigenschaft einen Gewinner bestimmt — nicht zu verwechseln mit der Vererbung.) Statt Selektoren aufzublähen, sagst du einmal, in welcher Reihenfolge deine Stil-Schichten gelten sollen — und danach gewinnt die spätere Schicht, egal wie schwach ihr Selektor ist.

Dieser Artikel ist die ausführliche Tour: die Regeln (jede davon im Browser nachgemessen, nicht aus der Doku abgeschrieben), die Umstellung einer real gewachsenen Website, Frameworks wie Bootstrap und Tailwind in eigenen Layern, das Verhältnis zu Web Components und Shadow DOM — und ein Muster, mit dem Design-Tokens ihre Layer-Zuordnung selbst mitbringen.

> **Methodik-Hinweis:** Alle Verhaltensbehauptungen in diesem Artikel wurden gegen einen echten Browser geprüft (Chromium-Engine, Edge 151, headless, `getComputedStyle`). Wo Dokumentation und Spezifikation sich widersprechen, steht hier das gemessene Ergebnis — an einer Stelle weiter unten ist genau das passiert.

## Das Problem: Spezifität ist ein schlechtes Ordnungssystem

Bevor `@layer` existierte, hattest du in CSS genau drei Hebel, um zu entscheiden, welche von zwei konkurrierenden Deklarationen gewinnt. **Deklaration** meint dabei genau ein `eigenschaft: wert;`-Paar; eine **Regel** ist der Selektor plus der geschweifte Block, der eine oder mehrere Deklarationen enthält. Die drei Hebel:

1. **Herkunft und Wichtigkeit** — Browser-Stylesheet, Nutzer-Stylesheet, Autoren-Stylesheet, plus `!important`.
2. **Spezifität** — die berühmte Zählung von ID-, Klassen- und Element-Selektoren. Der Browser zählt je Selektor drei Stellen: IDs — Klassen, Attribute und Pseudo-Klassen — Elemente und Pseudo-Elemente. `#hinweis` ergibt 1-0-0, `.box.box.box` ergibt 0-3-0, `div.box` ergibt 0-1-1. Verglichen wird von links nach rechts, der höhere Wert gewinnt.
3. **Reihenfolge im Quelltext** — bei Gleichstand gewinnt die letzte Deklaration.

Alle drei sind für kleine Stylesheets völlig ausreichend und für große Projekte ungeeignet. Der Grund ist strukturell: **Spezifität ist eine Eigenschaft des Selektors, nicht der Absicht.** Du willst sagen „diese Regel gehört zu meinem Design-System und darf von Komponenten überschrieben werden". Was du stattdessen sagen kannst, ist „diese Regel hat zwei Klassen und ein Element". Das sind zwei völlig verschiedene Aussagen, und CSS zwingt dich, die zweite zu benutzen, um die erste zu meinen.

Daraus folgt der bekannte Verlauf jedes größeren Projekts:

- Zuerst werden Selektoren länger, um zu gewinnen.
- Dann kommt `!important` dazu, weil Selektoren nicht mehr reichen.
- Dann kommt `!important` in der Gegenrichtung, weil das erste `!important` im Weg ist.
- Am Ende traut sich niemand mehr, irgendetwas zu löschen.

Methodiken wie BEM (Block Element Modifier), SMACSS (Scalable and Modular Architecture for CSS) oder ITCSS (Inverted Triangle CSS) sind allesamt Versuche, dieses Problem mit **Disziplin** zu lösen — über Konventionen für Namensgebung und Datei-Reihenfolge: Halte die Spezifität flach, sortiere die Datei so, dass Allgemeines zuerst und Wichtiges unten steht. Das funktioniert, solange sich alle daran halten — und bricht in dem Moment, in dem Fremd-CSS ins Spiel kommt, das sich naturgemäß nicht an deine Konvention hält.

Cascade Layers lösen das Problem an der richtigen Stelle: Sie machen die **Absicht** zu einem erstklassigen Bestandteil der Kaskade.

## Was Cascade Layers eigentlich sind

Ein Cascade Layer ist ein benannter Eimer für CSS-Regeln. Du legst einmal die Reihenfolge der Eimer fest, danach gilt:

> **Bei konkurrierenden normalen Deklarationen gewinnt der Eimer, der später deklariert wurde — unabhängig von der Spezifität der Selektoren darin.**

Das ist die ganze Grundidee. Alles Weitere sind Details und Sonderfälle.

Das Minimalbeispiel:

```css
@layer basis, komponenten;

@layer basis {
	#hinweis { color: red; }        /* Spezifität 1-0-0 */
}

@layer komponenten {
	.hinweis { color: green; }      /* Spezifität 0-1-0 */
}
```

Das Element wird **grün**. Der ID-Selektor verliert gegen einen Klassen-Selektor, weil `komponenten` nach `basis` deklariert wurde. In klassischem CSS wäre das schlicht unmöglich gewesen.

### Die Kaskade in voller Länge

Um `@layer` sicher einzusetzen, muss man wissen, *wo genau* Layer in der Kaskade sitzen. Die Spezifikation ([CSS Cascading and Inheritance Level 5](https://www.w3.org/TR/css-cascade-5/), englisch) sortiert konkurrierende Deklarationen in dieser Reihenfolge — der erste Punkt, der einen Unterschied macht, entscheidet:

1. **Herkunft und Wichtigkeit** (Browser / Nutzer / Autor, jeweils normal und `!important`)
2. **Kapselungskontext** (Shadow-DOM-Baum — dazu weiter unten ein eigenes Kapitel)
3. **Element-gebundene Styles** (das `style`-Attribut)
4. **Layer**
5. **Spezifität**
6. **Reihenfolge im Quelltext**

Zwei Dinge daran sind praktisch wichtig und werden oft übersehen:

**Layer stehen über Spezifität, aber unter Inline-Styles.** Ein `style="color: red"` im HTML schlägt jeden Layer. `@layer` ersetzt also nicht die Regel „keine Inline-Styles im Markup".

**Der Kapselungskontext steht noch über den Layern.** Das ist der Grund, warum `@layer` und Shadow DOM sich anders verhalten, als man zunächst annimmt — das Kapitel dazu kommt später.

Und die vollständige Rangfolge im Autoren-Bereich der Kaskade, von schwach nach stark — die beiden Animations-Ränge sind streng genommen eigene Herkunftsbereiche, die hier hineinragen:

| Rang | Was |
|---|---|
| 1 (schwächster) | Erster Layer, normal |
| 2 | Zweiter Layer, normal |
| … | weitere Layer, normal |
| 3 | **Ungelayerte** Regeln, normal |
| 4 | Inline-Styles, normal |
| 5 | Laufende CSS-Animationen |
| 6 | **Ungelayerte** Regeln, `!important` |
| … | weitere Layer, `!important` — **rückwärts** |
| 7 | Zweiter Layer, `!important` |
| 8 | Erster Layer, `!important` |
| 9 | Inline-Styles, `!important` |
| 10 (stärkster) | Laufende CSS-Transitions |

Die beiden Zeilen, die für Fehler sorgen, sind Rang 3 und Rang 6 — dazu gleich mehr.

## Die Syntax: fünf Formen, mehr gibt es nicht

### 1. Die Reihenfolge-Deklaration

```css
@layer reset, base, components, utilities;
```

Diese Zeile erzeugt vier leere Layer in genau dieser Reihenfolge. Sie sollte ganz oben in deinem Stylesheet stehen — sie ist der Vertrag, den der Rest der Datei erfüllt. Wo die Regeln später tatsächlich stehen, ist danach egal.

Das ist der wichtigste Trick an der ganzen At-Rule: **Die Reihenfolge wird durch die erste Erwähnung eines Layer-Namens festgelegt, nicht durch die Position der Regeln.** Du kannst deine Datei also weiter so sortieren, wie es für Menschen lesbar ist.

### 2. Der Layer-Block

```css
@layer components {
	.btn {
		border-radius: 0.4rem;
		padding: 0.5em 1em;
	}
}
```

Du darfst denselben Layer beliebig oft öffnen. Der Browser führt alle Blöcke desselben Namens zu einem Layer zusammen; innerhalb des Layers gelten dann wieder Spezifität und Quelltext-Reihenfolge.

```css
@layer components { .btn { padding: 0.5em 1em; } }
@layer utilities  { .p-0 { padding: 0; } }
@layer components { .card { padding: 1rem; } }   /* landet trotzdem vor utilities */
```

Das ist keine Schlamperei, sondern die vorgesehene Arbeitsweise für gewachsene Dateien — genau so ist auch die weiter unten beschriebene Umstellung von jpkc.com gelaufen.

### 3. Anonyme Layer

```css
@layer {
	/* Regeln ohne Layer-Namen */
}
```

Ein Block ohne Namen erzeugt einen Layer, den niemand mehr referenzieren, erweitern oder umsortieren kann. Jedes weitere `@layer { }` erzeugt einen *neuen* anonymen Layer. Nützlich für Code, den du bewusst abschotten willst; ungeeignet für alles, was du später noch anfassen musst. (Zu *überschreiben* ist ein anonymer Layer dagegen trivial — jeder spätere Layer und jede ungelayerte Regel schlägt ihn.)

### 4. Import in einen Layer

```css
@import url("bootstrap.css") layer(framework);
@import url("legacy.css") layer;            /* anonymer Layer */
```

Achte auf die zweite Zeile: Für den anonymen Layer steht das Schlüsselwort **ohne Klammern**. `layer()` mit leeren Klammern sieht plausibel aus, ist aber kein gültiger Wert — und das Fatale daran ist die Fehlerbehandlung: Der gesamte `@import` wird ungültig, das Stylesheet lädt **gar nicht**, und zwar ohne Konsolenfehler. Gemessen: Die importierten Regeln fehlen ersatzlos.

So bekommst du fremde Stylesheets in einen Layer, ohne ihren Inhalt anzufassen. Das ist der Kern des Framework-Kapitels weiter unten.

Wichtig zur Reihenfolge im Dokument: `@import` muss vor allen anderen Regeln stehen — **mit genau zwei Ausnahmen**, `@charset` und `@layer`-**Reihenfolgezeilen**. Gemeint ist dabei ausschließlich die Statement-Form `@layer a, b;`. Ein `@layer name { … }`-**Block** davor macht jedes folgende `@import` ungültig; die Spezifikation hält ausdrücklich fest, dass Layer-Blöcke nicht zwischen `@import`-Regeln stehen dürfen. Das folgende ist also gültig und auch die empfohlene Schreibweise:

```css
@layer framework, app;                          /* zuerst die Reihenfolge festnageln */
@import url("bootstrap.css") layer(framework);  /* dann importieren */
```

### 5. Verschachtelte Layer

```css
@layer components {
	@layer card { /* … */ }
	@layer button { /* … */ }
}

/* gleichbedeutend, mit Punktnotation: */
@layer components.card { /* … */ }
@layer components.button { /* … */ }
```

Verschachtelte Layer erzeugen eine Unter-Rangfolge innerhalb ihres Elternteils. Der Name ist dabei an den Elternteil gebunden: `components.card` und `utilities.card` sind zwei völlig verschiedene Layer.

Der Sinn ist Kapselung von Namensräumen. Ein Framework kann intern `framework.reset`, `framework.base`, `framework.components` benutzen, ohne dass sich diese Namen mit deinen eigenen `reset`/`base`/`components` beißen.

## Acht Regeln, die du kennen musst

Die folgenden acht Punkte sind das, was in der Praxis tatsächlich zu Überraschungen führt. Jeder ist unten mit dem gemessenen Ergebnis aus einem echten Browser belegt.

### Regel 1: Layer schlägt Spezifität — immer

```css
@layer a, b;
@layer b { #box { color: blue; } }                 /* 1-0-0 */
@layer a { .box.box.box { color: red; } }          /* 0-3-0 */
```

**Gemessen: blau.** Innerhalb eines Layers zählt Spezifität weiter ganz normal. Zwischen Layern zählt sie überhaupt nicht.

Das ist die Eigenschaft, wegen der man `@layer` einsetzt: Du kannst in einem späteren Layer mit einem einzigen Klassen-Selektor etwas überschreiben, das im früheren Layer mit einem ID-Selektor gesetzt wurde.

### Regel 2: Die Reihenfolge steht bei der ersten Erwähnung fest

```css
@layer a { #box { color: red; } }
@layer b { #box { color: green; } }
@layer b, a;                                       /* zu spät */
```

**Gemessen: grün.** Die Layer wurden bereits durch die beiden Blöcke in der Reihenfolge `a, b` angelegt. Die nachträgliche Deklarationszeile ändert nichts mehr — sie erwähnt nur zwei Layer, die es schon gibt.

Praktische Konsequenz: **Eine `@layer`-Reihenfolgezeile ist nur dann wirksam, wenn sie vor allen betroffenen Blöcken steht.** Deshalb gehört sie in die erste Zeile deines Einstiegs-Stylesheets — und in genau eine Datei, nicht in mehrere.

### Regel 3: Ungelayerte Styles gewinnen gegen alle Layer

```css
@layer alles { #box { color: blue; } }             /* 1-0-0, im Layer */
div.box { color: green; }                          /* 0-1-1, ohne Layer */
```

**Gemessen: grün.** Ungelayerte Regeln verhalten sich, als lägen sie in einem impliziten letzten Layer — und der letzte gewinnt.

Das ist gleichzeitig sehr praktisch und sehr gefährlich:

- **Praktisch**, weil du in einer Bestandscodebasis einfach anfangen kannst, Teile in Layer zu ziehen, ohne dass der noch ungelayerte Rest plötzlich schwächer wird. Migration ohne Big Bang.
- **Gefährlich**, weil jede Datei, die du *vergisst* zu layern, automatisch die höchste Priorität bekommt. Ein einziges vergessenes `<link>` auf ein Fremd-Stylesheet hebelt deine ganze Layer-Architektur aus. Dazu unten mehr.

### Regel 4: `!important` dreht die Reihenfolge um

Das ist die Regel, die am häufigsten für Verwirrung sorgt, und sie ist völlig konsistent gedacht: `!important` bedeutet in CSS seit jeher „Umkehrung der normalen Machtverhältnisse" — deshalb schlagen `!important`-Regeln des Nutzers die des Autors. Bei Layern gilt dasselbe Prinzip.

```css
@layer a, b;
@layer a { #box { color: red   !important; } }
@layer b { #box { color: blue  !important; } }
```

**Gemessen: rot.** Bei `!important` gewinnt der **frühere** Layer.

Und die Fortsetzung davon:

```css
@layer irgendwas { #box { color: blue !important; } }
#box { color: red !important; }                    /* ungelayert */
```

**Gemessen: blau.** Ungelayerte `!important`-Regeln sind unter allen Autoren-`!important`-Regeln die **schwächsten** — spiegelbildlich zu Regel 3.

Merke: Ein Layer, den du weit vorne einsortierst, ist bei normalen Deklarationen der schwächste — und bei `!important` der stärkste. Diese Doppelrolle ist der Hebel für das Bootstrap-Rezept weiter unten.

### Regel 5: Verschachtelte Layer verhalten sich wie ihr Elternteil im Kleinen

```css
@layer c.card   { #box { color: red;   } }
@layer c.button { #box { color: green; } }
```

**Gemessen: grün.** Innerhalb von `c` gilt dieselbe Logik: später deklariert gewinnt.

Und der Sonderfall, den man einmal gesehen haben muss:

```css
@layer c;
@layer c.child { #box { color: red;   } }
@layer c       { #box { color: green; } }   /* direkt in c, nicht in einem Kind */
```

**Gemessen: grün.** Regeln, die *direkt* im Elternteil liegen, gelten als impliziter letzter Unter-Layer — sie schlagen also alle benannten Kinder. Mit `!important` kehrt sich auch das um (dann gewinnt das erste Kind).

### Regel 6: `revert-layer` gibt Kontrolle an die vorherige Schicht zurück

```css
@layer basis, thema;
@layer basis { .btn { color: green; } }
@layer thema { .btn { color: red; }
               .btn.plain { color: revert-layer; } }
```

**Gemessen: grün** für `.btn.plain`. Der Wert wird nicht auf den Initialwert zurückgesetzt (das wäre `unset` beziehungsweise `initial`), sondern auf den, den die *vorherige* Schicht gesetzt hätte.

Zwei Randfälle, beide gemessen:

- **`revert-layer` im untersten Layer** fällt durch bis ins Browser-Stylesheet. Ein `<a href>` mit `color: revert-layer` im einzigen Layer wurde zu `rgb(0, 0, 238)` — dem Standard-Linkblau des Browsers.
- **`revert-layer` in ungelayertem CSS** fällt auf die Schicht darunter zurück — also auf den letzten expliziten Layer, der die Eigenschaft **überhaupt setzt**, nicht ins Browser-Stylesheet. Getestet mit zwei Layern `l1` (rot) und `l2` (grün) plus einer ungelayerten Regel `color: revert-layer` — Ergebnis: **grün**, also der Wert aus `l2`. Setzt `l2` die Eigenschaft nicht, fällt es weiter auf `l1` durch; setzt sie kein Layer, landet man doch beim Browser-Stylesheet.

  Der zweite Punkt ist bemerkenswert, weil verbreitete Zusammenfassungen hier etwas anderes behaupten (nämlich pauschal Rückfall auf das Browser-Stylesheet). Das gemessene Verhalten deckt sich mit der Spezifikation: Ungelayertes CSS *ist* der implizite letzte Layer, und „vorheriger Layer" ist entsprechend der letzte benannte. Es ist auch kein Chromium-Sonderweg — die Web-Platform-Tests decken genau diesen Fall ab (`css/css-cascade/revert-layer-002.html`), und er besteht in Chrome, Firefox und Safari.

Und eine Grenze: `revert-layer` ist eine normale Deklaration. Gegen ein `!important` aus einem anderen Layer kommt sie nicht an — auch das gemessen.

### Regel 7: Ein Layer, der nur deklariert wurde, hält seinen Platz

```css
@layer erst, zweit;
@layer zweit { #box { color: green; } }
@layer erst  { #box { color: red;   } }
```

**Gemessen: grün.** Die Deklarationszeile hat beide Layer angelegt; dass `zweit` textlich zuerst befüllt wird, spielt keine Rolle.

Ein Detail für Fortgeschrittene: Layer, die *innerhalb* einer `@media`-Bedingung angelegt werden, entstehen nur, wenn die Bedingung zutrifft. Damit hängt die Layer-Reihenfolge plötzlich vom Viewport ab. Das ist selten gewollt — deklariere die Reihenfolge unbedingt außerhalb aller Bedingungsblöcke.

### Regel 8: Animationen und Transitions stehen über allem

```css
@layer alles { #box { color: red; } }
#box { animation: farbe 100s step-end forwards; }
@keyframes farbe { from { color: green; } to { color: green; } }
```

**Gemessen: grün.** Eine laufende Animation schlägt jede normale Deklaration, gelayert oder nicht; eine laufende Transition schlägt sogar `!important`. Das ist kein Layer-Spezifikum, aber es erklärt einen Teil der Fälle, in denen `@layer` scheinbar „nicht wirkt".

## Praxis 1: Eine gewachsene Website auf Layer umstellen

Der Fall, den ich am besten belegen kann, ist die Hauptseite [jpkc.com](https://www.jpkc.com/). Deren CSS ist über Jahre gewachsen, hat einen Bootstrap-4-Erbteil im Reset und wird als **eine einzige Datei inline in jede Seite** geschrieben (rund 1.900 Zeilen, minifiziert per `lightningcss`). Ein klassischer Kandidat also: viel Bestand, keine Lust auf Neuschreiben.

Die Umstellung war ein einzelner Commit und bestand aus genau zwei Handgriffen.

**Erstens** die Reihenfolge festlegen, direkt nach den Custom Properties:

```css
@layer reset, base, components, utilities, responsive;
```

**Zweitens** die bestehenden Abschnitte in Blöcke wickeln — ohne eine einzige Regel zu verschieben:

| Layer | Was hineinkam |
|---|---|
| `reset` | `html`, `body`, `:target`, Scrollbars, die `:focus-visible`-Grundlinie |
| `base` | Typografie, Links, Code, Medien, Tabellen, Formulare, `::selection` |
| `components` | Buttons, Dropdown, SVG-Icons, Header, Hauptbereich, Navigation, `<details>`, Footer |
| `utilities` | `.visually-hidden`, Spacer, `.box`, Verzeichnisbaum-Liste, Light-Toggle |
| `responsive` | sämtliche `@media`-Blöcke — Breakpoints, `inverted-colors`, Print |

Die Datei behielt ihre Abschnittsreihenfolge. Weil sich mehrere Blöcke desselben Namens zusammenführen (Regel: Layer-Block), steht `@layer components { … }` an mehreren Stellen der Datei — das ist beabsichtigt und kein Fehler.

Drei Entscheidungen daran sind übertragbar:

**Die Tokens bleiben ungelayert.** Der `:root`-Block mit allen Custom Properties steht *vor* der Layer-Deklaration und außerhalb jedes Layers. (Custom Properties sind CSS-Variablen: mit `--name: wert` definiert, mit `var(--name)` gelesen; sie vererben sich an Nachfahren. `:root` ist die Pseudo-Klasse für das Wurzelelement, in HTML also `<html>`.) Damit haben die Design-Tokens unbedingte Priorität und können von keiner Komponente versehentlich überschrieben werden. Ob das die richtige Wahl ist, hängt vom Projekt ab — das Design-Token-Kapitel weiter unten diskutiert die Gegenposition.

**`responsive` als eigener Layer ist der eigentliche Gewinn.** Vorher galt: Ein `@media`-Block überschreibt eine Komponentenregel nur, wenn er im Quelltext weiter unten steht *und* mindestens dieselbe Spezifität hat. Beides musste man beim Bearbeiten im Kopf behalten. Jetzt gewinnt der Media-Block immer, weil sein Layer der letzte ist — auch mit einem simpleren Selektor. Genau das war zuvor die Fehlerquelle Nummer eins beim Anfassen alter Breakpoints.

**Die `!important`-Umkehr wurde bewusst protokolliert.** In der neuen Ordnung ist das `!important` in `.visually-hidden` (Layer `utilities`) *stärker* als jedes `!important` im Print-Stylesheet (Layer `responsive`). Aktuell gibt es keine Kollision, weil beide unterschiedliche Eigenschaften setzen — aber genau solche Konstellationen muss man beim Umstellen einmal durchsehen und notieren. Wer viele `!important` im Bestand hat, sollte die vor der Umstellung inventarisieren.

**Und die Kosten?** Exakt +83 Byte pro Seite nach der Minifizierung — auf jeder Seite dieselben 83 Byte, weil es schlicht die At-Rule-Zeilen sind. Bei einer Datei, die vollständig inline in jedes HTML-Dokument geht, sind das je nach Seitenlänge 0,06 % (Startseite) bis 0,18 % (kürzeste Unterseite). Für externe Stylesheets ist der Effekt durch Kompression noch kleiner. `@layer` kostet praktisch nichts.

Ein Detail zum Toolchain-Vertrauen: `lightningcss` **erhält** die Layer-Semantik beim Minifizieren, es entfernt die At-Rules nicht. Ein Minifier schreibt CSS auf minimale Größe um — benachbarte Regeln zusammenführen, Langform zu Kurzform verdichten —, und `lightningcss` ist so ein Werkzeug, geschrieben in Rust. Das ist nicht selbstverständlich und sollte bei jedem Build-Tool einmal am Output geprüft werden — ein Minifier, der `@layer`-Blöcke „wegoptimiert", kehrt die gesamte Kaskade um.

### Das Migrations-Rezept in Kurzform

1. `@layer`-Reihenfolge ganz oben deklarieren.
2. Bestehende Abschnitte *an Ort und Stelle* einwickeln, nichts verschieben.
3. Custom Properties bewusst platzieren (drinnen oder draußen — aber entschieden).
4. Alle `!important` im Projekt auflisten und die Umkehr gegenprüfen.
5. Bauen, minifizierten Output auf erhaltene `@layer`-Blöcke prüfen.
6. Visuell diffen. Wenn die Reihenfolge korrekt gewählt ist, ändert sich nichts — genau das ist das Ziel.

## Praxis 2: Bootstrap in einen eigenen Layer

Der eigentliche Star-Anwendungsfall von `@layer` ist Fremd-CSS. Nimm Bootstrap als Beispiel, weil es das verbreitetste ist und weil sein Verhalten sehr gut messbar ist.

Ich habe die aktuelle Distribution heruntergeladen und ausgezählt — Bootstrap 5.3.8, `dist/css/bootstrap.css`, rund 280 KB:

- **`@layer`-Vorkommen: 0.** Bootstrap 5 liefert keine Cascade Layers aus. Alles landet ungelayert — und damit nach Regel 3 *über* allem, was du selbst in Layer packst.
- **`!important`-Vorkommen: 1.716.** Der Löwenanteil kommt aus der Utility-API — einem Sass-Generator, der aus einer Konfigurations-Map Klassen wie `.m-1` oder `.text-danger` erzeugt. Eine **Utility-Klasse** hat genau einen Zweck und wird im Markup mit anderen kombiniert; `.mt-0` setzt `margin-top: 0`, `.text-center` zentriert den Text. Jede erzeugte Utility-Klasse trägt per Voreinstellung `!important`, damit sie ihre Komponente sicher überschreibt.

Diese beiden Zahlen beschreiben das Problem präzise. Wer Bootstrap anpassen will, kämpft nicht gegen dessen Spezifität, sondern gegen dessen Position in der Kaskade und gegen 1.716 `!important`.

### Schritt 1: Das Framework in einen frühen Layer

```css
@layer framework, app;

@import url("bootstrap.css") layer(framework);

@layer app {
	.btn {
		border-radius: 0;   /* gewinnt — ohne !important, ohne Selektor-Aufblähung */
	}
}
```

**Gemessen: funktioniert.** Eine einzelne Klasse in `app` schlägt Bootstraps Komponentenregeln, egal wie diese aufgebaut sind. Der gesamte Werkzeugkasten aus „noch eine Klasse davor" und „`.btn.btn.btn`" entfällt ersatzlos.

Das ist der Punkt, an dem das Arbeiten mit einem Framework qualitativ kippt: Du musst dessen Selektoren nicht mehr kennen, um sie zu überschreiben. Du musst nur wissen, dass dein Layer später kommt.

### Schritt 2: Die `!important`-Falle

Jetzt der Haken, den die meisten Anleitungen auslassen. Nach Regel 4 kehrt sich die Layer-Reihenfolge bei `!important` um. Bootstrap liegt in `framework`, also im *frühesten* Layer. Damit gilt:

**Bootstraps 1.716 `!important`-Deklarationen sind in dieser Anordnung stärker als deine eigenen `!important`-Deklarationen in `app`.**

Getestet mit Bootstraps `.text-danger { color: … !important }` gegen ein `!important` im späteren `app`-Layer: **Bootstrap gewinnt.** Das ist kein Bug, sondern die logische Folge von Regel 4 — aber es überrascht zuverlässig jeden, der es zum ersten Mal trifft.

### Schritt 3: Der Override-Layer

Die Lösung ist ein Layer, der **noch vor** dem Framework liegt und ausschließlich für `!important`-Gegenwehr da ist:

```css
@layer bs-overrides, bootstrap, app;

@import url("bootstrap.css") layer(bootstrap);

@layer bs-overrides {
	/* Nur !important-Regeln, die Bootstrap-Utilities aushebeln sollen. */
	.text-danger { color: var(--brand-danger) !important; }
}

@layer app {
	/* Alles Normale — schlägt Bootstrap ohne !important. */
	.btn { background: var(--brand); }
}
```

**Gemessen: beides funktioniert.** `bs-overrides` gewinnt das `!important`-Duell (früherer Layer), `app` gewinnt das normale Duell (späterer Layer). Ein Layer für jede Richtung.

Das wirkt zunächst wie ein Kunstgriff, ist aber sauber begründbar: Du hast zwei verschiedene Absichten — „ich möchte den Normalfall ergänzen" und „ich möchte eine Notbremse des Frameworks aushebeln" — und in einer Kaskade mit Umkehr brauchen zwei Absichten zwei Plätze.

**Die bessere Lösung, wenn du Bootstrap selbst kompilierst:** Schalte die `!important` an den Utilities einfach ab. Bootstrap hat dafür eine Sass-Variable — Sass ist ein CSS-Präprozessor mit eigener Sprache (Variablen mit `$name`, Mixins, Verschachtelung), den ein Build-Schritt zu CSS kompiliert; SCSS ist dessen CSS-nahe Schreibweise:

```scss
$enable-important-utilities: false;
@import "bootstrap/scss/bootstrap";
```

Ohne `!important` an den Utilities entfällt das gesamte zweite Layer-Problem, und die reine Layer-Reihenfolge reicht. Diese Variable gibt es seit Bootstrap 5.0 und sie steht in `scss/_variables.scss` auf `true !default`.

### Der Sass-Fallstrick

Wer Bootstrap per Sass einbindet, stößt auf eine echte Werkzeug-Grenze: **`@use` darf in Sass nicht innerhalb eines Blocks stehen.** (`@use` lädt eine Sass-Datei als Modul, dessen Variablen, Mixins und Funktionen unter einem Namensraum bereitstehen; `@forward` reicht sie weiter. Beide ersetzen das abgekündigte `@import`.) Das hier ist also kein gültiges Sass:

```scss
@layer bootstrap {
	@use "bootstrap/scss/bootstrap";   /* Fehler: @use muss auf oberster Ebene stehen */
}
```

Ein `layer()`-Argument für `@use` und `@forward` war 2022 zwar vorgeschlagen, wurde aber binnen weniger Tage abgelehnt — mit dem Hinweis, dass Sass dafür bereits ein Werkzeug hat. Genau das ist der saubere Weg:

**`meta.load-css()` lädt ein Sass-Modul dort, wo du gerade stehst** — also auch innerhalb eines `@layer`-Blocks:

```scss
@use "sass:meta";

@layer bootstrap {
	@include meta.load-css("bootstrap/scss/bootstrap");
}
```

Getestet mit Dart Sass 1.102.0: Das kompiliert zu `@layer bootstrap { … }` mit dem vollständigen Framework-CSS darin — und ohne Deprecation-Warnung. Wichtig ist der Unterschied zu `@use`: `meta.load-css()` gibt nur das **CSS** aus, es stellt dir Variablen, Mixins und Funktionen des Moduls **nicht** zur Verfügung. Die lädst du weiterhin oben per `@use` — und genau diese Arbeitsteilung ist der Punkt.

Damit hast du drei gangbare Wege, in dieser Reihenfolge:

1. **`meta.load-css()` innerhalb des Layers.** Der offizielle Weg, kein Verfallsdatum, keine zusätzliche Datei. Erste Wahl, wenn das Framework ohnehin durch deine Sass-Pipeline läuft.
2. **Framework separat kompilieren.** Bootstrap zu einer eigenen `.css`-Datei bauen und per `@import url(…) layer(bootstrap)` aus einem schlanken CSS-Einstiegspunkt holen. Sauberste Trennung, unabhängig von Sass — sinnvoll, wenn du das Framework gar nicht anpasst.
3. **Nach dem Build umwickeln.** Ein PostCSS-Schritt legt den kompletten kompilierten Framework-Output in einen Layer. Robust und toolchain-unabhängig, kostet aber einen zusätzlichen Build-Schritt.

Das früher übliche verschachtelte `@import` innerhalb `@layer { }` funktioniert weiterhin, wirft aber eine Deprecation-Warnung — `@import` wird in Dart Sass 3.0 entfernt. Für neuen Code also nicht mehr verwenden.

### Der `<link>`-Fallstrick

Und jetzt der Fehler, der eine ganze Layer-Architektur lautlos wertlos macht.

**Ein `<link rel="stylesheet">` kann heute keinem Layer zugewiesen werden.** Es gibt keinen `layer`-Attributwert für `<link>`. Der entsprechende Vorschlag liegt seit Jahren bei der CSS Working Group, ist aber in keinem Browser umgesetzt.

Die Konsequenz ist nach Regel 3 hart:

```html
<link rel="stylesheet" href="fremd.css">   <!-- ungelayert -->
<style>
	@layer app { html body #box { color: blue; } }   /* Spezifität 0-1-0-2 */
</style>
```

```css
/* fremd.css */
#box { color: red; }                                 /* Spezifität 0-1-0-0 */
```

**Gemessen: rot.** Die schwächere Regel aus dem verlinkten Stylesheet gewinnt, weil sie ungelayert ist. Deine sorgfältig gebaute Layer-Ordnung ist gegen sie machtlos.

Der Workaround ist ein Zwischen-Stylesheet: Eine winzige CSS-Datei, die nichts tut, außer die eigentlichen Dateien in Layer zu importieren.

```css
/* main.css — die einzige Datei, die per <link> geladen wird */
@layer reset, framework, base, components, utilities;

@import url("reset.css")      layer(reset);
@import url("bootstrap.css")  layer(framework);
@import url("base.css")       layer(base);
@import url("components.css") layer(components);
@import url("utilities.css")  layer(utilities);
```

Damit hast du aber ein Performance-Problem eingetauscht: `@import`-Ketten werden **seriell** aufgelöst. Der Browser muss `main.css` vollständig laden und parsen, bevor er überhaupt weiß, dass er `bootstrap.css` braucht — und CSS blockiert das Rendering, zeigt also nichts an, bevor es geladen und geparst ist (sonst blitzte kurz ungestyltes HTML auf). Bei fünf Importen wartest du auf mindestens zwei volle Roundtrips — ein Roundtrip ist ein kompletter Weg Anfrage-Antwort zum Server und zurück.

Die praktikable Antwort für Produktion: **Bündeln zur Build-Zeit.** Dein Bundler (esbuild, Vite, Lightning CSS, PostCSS — Letzteres kein Präprozessor mit eigener Sprache wie Sass, sondern ein Werkzeug, das CSS im Build über JavaScript-Plugins umschreibt) löst die `@import`-Anweisungen auf und schreibt die `@layer`-Blöcke direkt in eine Datei. Du behältst die saubere Quellstruktur, der Browser bekommt eine einzige Anfrage. Wer `@import` zur Laufzeit stehen lässt, sollte die betroffenen Dateien zumindest per `<link rel="preload">` anstoßen.

## Praxis 3: Tailwind CSS v4 — die Layer sind schon da

Wer Tailwind in Version 4 einsetzt, arbeitet bereits mit echten Cascade Layers, ob er will oder nicht. Tailwind v3 hatte ein `@layer`-Konstrukt, das nur *aussah* wie die CSS-At-Rule: Es war eine PostCSS-Direktive, die zur Build-Zeit ausgewertet und wieder entfernt wurde. Version 4 nutzt native Layer und schreibt sie in den Output.

Das lässt sich am Build dieser Seite hier direkt zeigen. Der Kopf der generierten `main.css` (Tailwind 4.3.0):

```css
/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
@layer properties { … }
@layer theme      { … }
@layer base       { … }
@layer components { … }
@layer utilities  { … }
```

Fünf Layer in dieser Reihenfolge. Der erste davon ist bedarfsabhängig: `properties` erscheint nur, wenn die erzeugten Utilities tatsächlich registrierte Custom Properties brauchen — ein Minimal-Build ohne solche Klassen gibt nur `theme, base, components, utilities` aus. Was das für deinen eigenen Code bedeutet:

**Eigenes CSS ohne Layer schlägt alle Tailwind-Utilities.** Das folgt aus Regel 3 und ist die häufigste Ursache für „meine Utility-Klasse wirkt nicht". Wenn du eine eigene Komponentendatei per `@import` neben Tailwind lädst und sie in keinem Layer liegt, gewinnt sie gegen `@layer utilities` — und damit gegen `p-4`, `text-center` und alles andere.

Die Lösung ist, eigenes CSS ebenfalls einzuordnen:

```css
@import "tailwindcss";

/* Eigene Komponenten unterhalb der Utilities einsortieren, damit
   Utility-Klassen im Markup weiterhin das letzte Wort haben. */
@layer components {
	.prose-card {
		border-radius: 0.75rem;
		padding: 1.5rem;
	}
}
```

**Für neue Utilities gibt es einen eigenen Weg.** Tailwind v4 bringt die Direktive `@utility` mit, die eine Klasse als echte Utility registriert — du schreibst sie direkt in deine CSS-Datei (`@utility tab-4 { tab-size: 4; }`). Dazu gehören Varianten-Unterstützung (die Präfixe `hover:` und `md:`, die eine Utility an einen Zustand oder einen Breakpoint binden) und die korrekte Einsortierung in den `utilities`-Layer. Ein handgeschriebenes `@layer utilities { … }` bekommt das nicht mit.

**Und ein Framework über dem Framework?** Wenn du Tailwind mit einem weiteren Fremd-Stylesheet kombinierst, kannst du die gesamte Tailwind-Ausgabe in einen übergeordneten Layer verschieben, indem du den Import layerst:

```css
@layer vendor, tw, app;
@import "tailwindcss" layer(tw);
```

Tailwinds eigene Layer werden dadurch zu `tw.theme`, `tw.base`, `tw.components` und `tw.utilities` — die interne Reihenfolge bleibt erhalten, der Block rückt als Ganzes zwischen `vendor` und `app`. Das ist die verschachtelte Namensraum-Kapselung aus dem Syntax-Kapitel in ihrer nützlichsten Form.

**Eine Ausnahme gibt es dabei**, und sie ist am realen Build nachgemessen: `properties` entkommt der Verschachtelung. Der Layer bleibt auf oberster Ebene und wird sogar noch vor `vendor` deklariert — er ist damit der schwächste im ganzen Dokument. Das ist unkritisch, weil er nur Ausgangswerte liefert, aber es heißt: Von den fünf Layern wandern nur vier in den Namensraum.

Womit auch geklärt wäre, was `properties` überhaupt tut: Er enthält einen `@supports`-Block, der registrierte Custom Properties dort nachbildet, wo der Browser sie noch nicht kennt. Die Bedingung zielt auf zwei Engines gleichzeitig — ältere Safari- **und** ältere Firefox-Versionen (vor 128).

## Praxis 4: Fremd-Widgets, Embeds und Legacy-Code

Der dritte klassische Anwendungsfall ist alles, was du nicht kontrollierst und nicht anfassen willst: eingebettete Buchungs-Widgets, Consent-Banner, Chat-Fenster, das CSS eines alten Bereichs, den niemand mehr versteht.

Der Reflex ist hier der **anonyme Layer** — und genau daran lässt sich eine Falle zeigen, in die man erstaunlich leicht tappt. Dieses Beispiel tut **nicht**, was es verspricht:

```css
@layer app;                         /* ❌ app ist jetzt der ERSTE Layer */
@import url("widget.css") layer;    /* ❌ der anonyme Layer entsteht DANACH */
```

Gemessen: Das Widget gewinnt. Die Erklärung ist Regel 2 — die Reihenfolge steht bei der ersten Erwähnung fest. `@layer app;` erwähnt `app` zuerst und macht es damit zum schwächsten Layer; der anonyme Layer des Imports entsteht danach und ist folglich stärker. Anonyme Layer haben genau diese Eigenheit: Weil sie keinen Namen tragen, kannst du sie in **keiner** Reihenfolgezeile vorziehen. Ihre Position ergibt sich allein daraus, wo der Import steht.

Richtig ist deshalb ein **benannter** Vendor-Layer, der in der Reihenfolgezeile ganz vorne steht:

```css
@layer widget, app;                        /* Fremdcode zuerst = schwächster Layer */

@import url("widget.css") layer(widget);

@layer app {
	/* dein Code — gewinnt jede normale Auseinandersetzung */
}
```

**Gemessen: funktioniert.** Du legst den Fremdcode in einen Layer, der ganz vorne steht, und musst ihn danach nie wieder erwähnen. Niemand erweitert ihn versehentlich, und dein eigenes CSS gewinnt jede normale Auseinandersetzung.

Wenn der Layer wirklich anonym bleiben soll, muss der Import **vor** allem Eigenen stehen — dann stimmt die Reihenfolge, aber du gibst die Möglichkeit auf, sie später explizit zu dokumentieren. In der Praxis ist der benannte Layer fast immer die bessere Wahl.

Für Legacy-Code im eigenen Haus ist ein benannter Layer besser:

```css
@layer legacy, modern;

@layer legacy {
	/* Der alte Bereich, unverändert hineinkopiert. */
}
```

Der Effekt ist ein technischer Schuldenschnitt: Alles in `legacy` verliert automatisch gegen alles Neue. Du kannst alten Code liegenlassen, statt ihn zu entschärfen — und Stück für Stück herausziehen, ohne dass sich zwischendurch die Prioritäten verschieben.

## Web Components: was Layer können — und was nicht

Kurz zum Vokabular, bevor es losgeht: **Web Components** ist der Sammelbegriff für eigene HTML-Elemente mit gekapseltem Innenleben. Ein **Custom Element** ist so ein selbst definiertes Element (Name mit Bindestrich, per `customElements.define()` registriert); an ihm hängt ein **Shadow DOM** — ein eigener, abgeschotteter DOM-Baum, dessen Styles gescopt sind und in den normale Dokument-Selektoren nicht hineingreifen.

Hier wird es interessant, denn die verbreitete Erwartung ist falsch. Wer `@layer` als „Prioritätssystem für CSS" verstanden hat, nimmt an, dass sich damit auch die Grenze zwischen Seite und Web Component regeln lässt. Das geht nicht — und zwar aus einem sehr klaren Grund.

### Der Kapselungskontext steht über den Layern

Erinnere dich an die Sortierreihenfolge aus dem zweiten Kapitel: **Kapselungskontext** steht auf Platz 2, **Layer** erst auf Platz 4. Kapselungskontext heißt schlicht: aus welchem Baum die Deklaration stammt — Dokument oder Shadow-Baum. Der Browser prüft also zuerst, aus welchem Baum eine Deklaration stammt, und kommt zur Layer-Frage überhaupt nur, wenn beide aus demselben Baum kommen.

Die Regel für den Kapselungskontext lautet:

> Bei **konkurrierenden** Deklarationen für dasselbe Element gewinnt bei **normalen** Deklarationen der **äußere** Baum, bei **`!important`** der **innere**.

Das Wort „konkurrierend" trägt hier Gewicht. Die Regel greift nur, wo überhaupt zwei Deklarationen um dasselbe Element streiten — also bei `:host`, `::part()` und `::slotted()`. Überall dort, wo das Dokument die Elemente im Shadow-Baum gar nicht selektieren kann, gewinnt die Komponente ohnehin, und ein geerbter Wert wird von jeder Shadow-Regel geschlagen.

Gemessen an einem Custom Element mit offenem Shadow Root:

```html
<style>
	#karte { color: green; }              /* Dokument, normal */
</style>
<my-card id="karte">…</my-card>
```

```js
shadow.innerHTML = `<style>
	@layer a, b;
	@layer b { :host { color: red; } }    /* Shadow-Baum, normal, letzter Layer */
</style><slot></slot>`;
```

**Gemessen: grün.** Die Dokumentregel gewinnt — obwohl die Shadow-Regel im späteren Layer ihres Baums liegt. Der Layer wurde gar nicht erst befragt.

Und die Gegenprobe mit `!important`:

```html
<style>#karte { color: green !important; }</style>
```

```js
shadow.innerHTML = `<style>:host { color: blue !important; }</style><slot></slot>`;
```

**Gemessen: blau.** Jetzt gewinnt der innere Baum.

### Layer-Namen überqueren keine Shadow-Grenze

Der zweite wichtige Punkt: **Jeder Shadow-Baum hat seine eigene, völlig unabhängige Layer-Reihenfolge.** Ein `@layer components` im Dokument und ein `@layer components` im Shadow Root sind zwei verschiedene Dinge, die nichts voneinander wissen.

Gemessen: Deklariert das Dokument `@layer b, a;` und der Shadow-Baum erwähnt ohne eigene Reihenfolgezeile erst `a` und dann `b`, so gilt im Inneren die dortige Erstnennung — die Reihenfolgezeile des Dokuments bleibt wirkungslos.

Das ist konsistent mit dem Zweck von Shadow DOM: Kapselung ist genau die Zusage, dass äußere Struktur innen nichts bedeutet. Zwei Präzisierungen gehören aber dazu, weil die Kurzfassung „Layer und Shadow DOM haben nichts miteinander zu tun" in beide Richtungen zu grob ist.

**Erstens: Ein globales `@layer reset` erreicht Shadow-Bäume teilweise sehr wohl.** Selektor-Gebundenes bleibt draußen — `* { box-sizing: border-box }` aus dem Dokument wirkt im Shadow-Baum nicht. **Vererbbare** Eigenschaften fließen dagegen wie eh und je hinein: Ein `font-family: monospace` und ein `letter-spacing: 3px` aus dem Dokument-Reset waren im Shadow-Baum messbar wirksam. Da ein realer Reset aus beidem besteht, kommt sein Typografie-Teil vollständig an.

**Zweitens: Eine Layer-API nach außen ist möglich — aber nur über den Baum, nicht über die Dokumentgrenze.** Solange dein CSS im Dokument liegt, kannst du der Komponente keinen Layer unterschieben; der Kapselungskontext entscheidet vorher. Landet dein Stylesheet dagegen *im* Shadow-Baum — weil die Komponente ein `CSSStyleSheet` entgegennimmt und adoptiert, oder weil ihr Shadow Root offen ist und du ein `<style>` hineinhängst —, teilt es den internen Layer-Namensraum. Gemessen: Ein hineingegebenes `@layer a { … }` landet dann tatsächlich im vorhandenen internen Layer `a` und ordnet sich dessen Rang unter, statt einen neuen Layer am Ende anzulegen.

Das ist die technische Grundlage des verbreiteten Theme-Injection-Musters — und ein Vertrag, den die Komponente bewusst eingehen muss. In der CSS Working Group laufen dazu weitergehende Vorschläge; verlass dich für Produktionscode noch nicht darauf.

### `:host` ist absichtlich schwach

Aus der Kontext-Regel folgt eine Eigenschaft, die man beim Bau von Komponenten kennen muss: **`:host`-Regeln sind für den Konsumenten leicht zu überschreiben.** (`:host` selektiert von innen heraus das Element, an dem der Shadow Root hängt — wirksam nur in Stilen, die innerhalb dieses Shadow Roots stehen.) Jede Dokumentregel, die das Host-Element trifft, gewinnt gegen jede normale `:host`-Regel — unabhängig von Spezifität und Layern.

Das ist eine bewusste Design-Entscheidung der Plattform: `:host` setzt *Vorgaben*, keine *Vorschriften*. Der Versuch, das mit Spezifität zu erzwingen, scheitert zuverlässig — auch `:host(.a.b.c.d.e)` und `:host-context(body)` verlieren gegen einen simplen `#id` im Dokument.

Wer als Komponentenautor etwas wirklich festnageln muss (etwa `display` oder `box-sizing`, damit das Layout nicht bricht), hat zwei Möglichkeiten:

- **`!important` auf `:host`** — und bekommt es dank der Kontext-Umkehr durchgesetzt. Wie stark das ist, zeigt ein Nebenbefund: `:host { … !important }` schlägt sogar ein `!important` im `style`-Attribut des Host-Elements. Es ist die einzige Stelle im ganzen Artikel, an der Inline-Styles verlieren — weil der Kapselungskontext in der Sortierreihenfolge noch vor den element-gebundenen Styles geprüft wird.
- **Gar nicht auf `:host` setzen.** Leg die kritische Eigenschaft stattdessen auf einen inneren Wrapper im Shadow-Baum. Ohne `::part()` hat das Dokument dort schlicht keinen Selektor, der hinreicht — der robustere Weg, wenn es nur darum geht, dass das Layout nicht bricht.

### Wofür Layer in Web Components dann gut sind

Innerhalb des Shadow Roots sind Cascade Layers uneingeschränkt nützlich, und zwar für genau denselben Zweck wie im Dokument: interne Ordnung.

Ein Wort zum verwendeten Mechanismus: `adoptedStyleSheets` hängt ein per JavaScript gebautes `CSSStyleSheet` an einen Shadow Root. Der Vorteil gegenüber einem `<style>`-Tag ist, dass sich ein Blatt von vielen Instanzen derselben Komponente teilen lässt.

```js
const sheet = new CSSStyleSheet();
sheet.replaceSync(`
	@layer defaults, parts, state;

	@layer defaults {
		:host { display: block; box-sizing: border-box; }
		button { all: unset; cursor: pointer; }
	}

	@layer parts {
		button { padding: 0.5em 1em; border-radius: 0.4rem; }
	}

	@layer state {
		:host([disabled]) button { opacity: 0.5; cursor: not-allowed; }
	}
`);
shadow.adoptedStyleSheets = [sheet];
```

Der Nutzen ist derselbe wie überall: Der Zustands-Layer gewinnt gegen den Teile-Layer, ohne dass du `:host([disabled]) button` künstlich spezifischer machen musst. In einer Komponente mit vielen Zuständen (`[disabled]`, `[loading]`, `[aria-invalid]`) spart das genau die verschachtelten Selektoren, die Komponenten-CSS sonst unlesbar machen.

Ein Detail zu `adoptedStyleSheets`: Adoptierte Stylesheets werden **nach** den `<style>`-Elementen desselben Baums einsortiert. Bei Gleichstand in allem anderen gewinnt also das adoptierte Blatt. Über das Zusammenspiel von adoptierten Stylesheets, Layern und teilweise offenen Shadow Roots wird in der CSS Working Group aktiv diskutiert; das ist derzeit noch Bewegung im Standard und kein Fundament für Produktionscode.

### Die Kurzfassung für Komponentenautoren

| Frage | Antwort |
|---|---|
| Layer *innerhalb* der Komponente nutzen? | Ja, uneingeschränkt sinnvoll |
| Layer als Styling-API nach außen anbieten? | Nur über einen expliziten Vertrag — adoptiertes Stylesheet oder offener Shadow Root |
| Vom Dokument aus in einen Komponenten-Layer schreiben? | Nein — der Kapselungskontext entscheidet vorher |
| `:host`-Vorgaben vor Überschreiben schützen? | Mit `!important` — oder gar nicht erst auf `:host` setzen, sondern auf einen internen Wrapper |
| Globale Layer-Reihenfolge beeinflusst die Komponente? | Ihre **interne** Rangfolge nicht. Alles, was von außen ankommt (`::part()`, geslottete Inhalte, geerbte Werte), sehr wohl |
| Was ist die richtige Styling-API? | Custom Properties und `::part()` — beide funktionieren wie vorher |

Zu `::part()`: Die Komponente markiert innere Elemente mit `part="label"`; von außen stylst du sie dann per `my-card::part(label) { … }`. Das ist der offizielle, vom Autor der Komponente freigegebene Weg nach innen.

## Design-Tokens mit Layer-Metadaten

Zum Schluss ein fortgeschrittenes Muster, das die Layer-Zuordnung dahin verlagert, wo sie hingehört: in die Datenquelle.

### Das Problem

Wenn Design-Tokens von Hand als CSS geschrieben werden, ist die Layer-Frage trivial — du tippst `@layer` und fertig. Design-Tokens sind benannte Design-Entscheidungen — Farben, Abstände, Schriftgrößen — zentral in einer Datei gepflegt, meist als JSON. In jedem ernsthaften Design-System werden Custom Properties daraus **generiert**, über einen Build-Schritt. Und diese Token-Datei kennt keine Layer:

```json
{
	"card": {
		"background": { "$value": "#1c2935", "$type": "color" }
	}
}
```

Der Generator hat keine Möglichkeit zu wissen, dass dieses Token zur Card-Komponente gehört und entsprechend in einen Komponenten-Layer sollte. Also landet üblicherweise alles in einem einzigen `:root`-Block — und die gesamte Layer-Struktur des Systems endet an der Token-Grenze. Bei zwanzig Komponenten fällt das nicht auf; bei zweihundert wird es zu genau dem Ordnungsproblem, das `@layer` eigentlich lösen sollte.

### `$extensions` statt eines eigenen Schlüssels

Der naheliegende Reflex wäre, einfach ein Feld zu erfinden:

```json
{ "$value": "#1c2935", "$type": "color", "cssLayer": "components.card" }
```

Das ist eine schlechte Idee, und der Grund steht in der Spezifikation. Im Token-Format kennzeichnet das Dollarzeichen reservierte Schlüssel — `$value`, `$type`, `$description` —, und das [Design Tokens Format Module](https://www.designtokens.org/TR/drafts/format/) (englisch) der Design Tokens Community Group (DTCG) beim W3C sieht für eigene Zusatzdaten das Feld **`$extensions`** vor — mit einer harten Zusage und einer Empfehlung.

Die harte Zusage ist eine Erhaltungspflicht:

> „Tools that process design token files **MUST** preserve any extension data they do not themselves understand."

Ein Werkzeug, das deine Token-Datei liest und neu schreibt, **muss** unbekannte `$extensions` erhalten. Für einen selbst erfundenen Schlüssel auf oberster Ebene gibt es diese Garantie nicht — er darf beim nächsten Durchlauf durch ein fremdes Werkzeug verschwinden.

Die Empfehlung betrifft die Benennung:

> „The keys **SHOULD** be chosen such that they avoid the likelihood of a naming clash with another vendor's data. The reverse domain name notation is recommended for this purpose."

Umgekehrte Domain-Notation heißt: die Bestandteile deiner Domain in umgekehrter Reihenfolge schreiben. Aus `jpkc.com` wird `com.jpkc` — ein Namensraum, der eindeutig einem Anbieter gehört. Aus Java und iOS kennst du das Muster.

Eine dritte Stelle derselben Sektion ist ein Dämpfer, den man kennen sollte: Die Spezifikation bittet darum, `$extensions` auf **optionale** Metadaten zu beschränken, die zum Verständnis des Token-*Werts* nicht nötig sind. Das ist hier erfüllt — die Farbe `#1c2935` steht für sich, auch ohne zu wissen, in welchen Layer sie gehört. Aber es heißt auch: Geht die Extension unterwegs verloren, ist dein Build nicht falsch, sondern nur ungelayert. Das merkt niemand beim Draufschauen — also lass es deine CI prüfen.

Damit sieht eine Token-Datei mit Layer-Zuordnung so aus — hier vollständig, damit du den Output weiter unten nachvollziehen kannst. Die äußere Gruppe `color` bestimmt das Präfix der erzeugten Custom Properties; `brand` bleibt bewusst ohne Layer-Angabe und landet später im ungelayerten `:root`:

```json
{
	"color": {
		"brand": { "$value": "#2f5468", "$type": "color" },
		"card": {
			"background": {
				"$value": "#1c2935",
				"$type": "color",
				"$extensions": {
					"com.jpkc/css-layer": { "layer": "components.card" }
				}
			}
		},
		"button": {
			"background": {
				"$value": "#8fb3c6",
				"$type": "color",
				"$extensions": {
					"com.jpkc/css-layer": { "layer": "components.button" }
				}
			}
		}
	}
}
```

Der Token-Wert bleibt plattformneutral — für iOS oder Android ist die Layer-Angabe schlicht unbekanntes Beiwerk, das ignoriert (aber erhalten) wird. Nur der CSS-Generator wertet sie aus. Das ist sauberer als jede Lösung, die CSS-Konzepte in die Token-Werte selbst schreibt.

### Der Generator

Der zweite Teil ist ein Format-Hook, der die Tokens nach ihrer Layer-Angabe gruppiert. In [Style Dictionary](https://styledictionary.com/) (englisch) — dem verbreitetsten Token-Compiler — sind das rund 30 Zeilen. Ein Token-Compiler liest deine Token-Datei und erzeugt daraus pro Plattform passende Dateien: CSS-Custom-Properties fürs Web, Konstanten für iOS und Android. Der folgende Code ist gegen Style Dictionary 5.5.1 getestet, nicht geschätzt:

```js
import StyleDictionary from 'style-dictionary';

const EXT_KEY = 'com.jpkc/css-layer';

StyleDictionary.registerFormat({
	name: 'css/variables-layered',
	format: ({ dictionary, options }) => {
		// Welche Form die Tokens haben, hängt an der Quelldatei — SD meldet es hier.
		const dtcg = options.usesDtcg;
		const val = (t) => (dtcg ? t.$value : t.value);
		const ext = (t) => (dtcg ? t.$extensions : t.extensions) ?? t.original?.$extensions;

		const layers = new Map();
		const plain = [];

		for (const token of dictionary.allTokens) {
			const layer = ext(token)?.[EXT_KEY]?.layer;
			if (!layer) { plain.push(token); continue; }
			if (!layers.has(layer)) layers.set(layer, []);
			layers.get(layer).push(token);
		}

		const decl = (t, pad) => `${pad}--${t.name}: ${val(t)};`;
		let out = '';

		// Reihenfolge explizit voranstellen — sonst entscheidet die Token-Sortierung.
		if (layers.size) out += `@layer ${[...layers.keys()].join(', ')};\n\n`;
		if (plain.length) out += `:root {\n${plain.map((t) => decl(t, '\t')).join('\n')}\n}\n\n`;

		for (const [name, tokens] of layers) {
			out += `@layer ${name} {\n\t:root {\n${tokens.map((t) => decl(t, '\t\t')).join('\n')}\n\t}\n}\n\n`;
		}
		return out;
	},
});

const sd = new StyleDictionary({
	source: ['tokens/tokens.json'],
	usesDtcg: true,                        // optional — SD erkennt DTCG auch selbst
	platforms: {
		css: {
			transformGroup: 'css',
			buildPath: 'build/',
			files: [{ destination: 'tokens.css', format: 'css/variables-layered' }],
		},
	},
});

await sd.buildAllPlatforms();
```

Der erzeugte Output:

```css
@layer components.card, components.button;

:root {
	--color-brand: #2f5468;
}

@layer components.card {
	:root {
		--color-card-background: #1c2935;
	}
}

@layer components.button {
	:root {
		--color-button-background: #8fb3c6;
	}
}
```

Zwei Details, die den Unterschied zwischen „funktioniert im Beispiel" und „funktioniert im Projekt" ausmachen:

**`usesDtcg` musst du meist gar nicht setzen — du darfst es nur nicht raten.** Style Dictionary erkennt das DTCG-Format an der Quelldatei selbst; lässt man die Option weg, ist der Output byte-identisch (nachgemessen). Sie explizit zu setzen dokumentiert also nur deine Erwartung.

Gefährlich ist die falsche Annahme *im Hook*. Liest ein Hook, der `token.value` erwartet, eine DTCG-Datei mit `$value`, entsteht ein Output voller `--foo: undefined;` — ohne Fehlermeldung, Exit-Code 0. Und wer die Option gegen die Quelldatei erzwingt (`usesDtcg: false` auf einer `$value`-Datei), bekommt eine **leere** Ausgabedatei, ebenfalls ohne Fehler. Genau deshalb liest der Hook oben `options.usesDtcg` aus, statt eine der beiden Formen anzunehmen — dann funktioniert er mit alten und neuen Token-Dateien gleichermaßen.

**Die Layer-Reihenfolge muss explizit vorangestellt werden.** Ohne die einleitende `@layer …;`-Zeile entstünde die Reihenfolge zufällig aus der Sortierung der Tokens in der Quelldatei. Ein umbenanntes Token könnte dann die Kaskade umkrempeln — der denkbar unangenehmste Bug, weil die Ursache in einer JSON-Datei liegt und die Wirkung im gerenderten Layout.

### Der Haken: Tokens im Layer sind schwächer

Und jetzt der Teil, der bei diesem Muster ehrlicherweise dazugehört, weil er das Ergebnis in manchen Projekten unbrauchbar macht.

Sobald ein Token in einem Layer liegt, gilt für es Regel 3: **Jede ungelayerte `:root`-Regel schlägt es.** Gemessen:

```css
@layer theme { :root { --brand: red; } }
:root { --brand: green; }                /* ungelayert */
```

Der aufgelöste Wert von `--brand` ist **grün**. Wer Themes über einen ungelayerten Block umschaltet — und das ist ein extrem verbreitetes Muster —, überfährt damit sämtliche gelayerten Tokens auf einen Schlag:

```css
/* Kippt jedes gelayerte Token, ohne dass es jemand merkt: */
@media (prefers-color-scheme: dark) {
	:root { --brand: #8fb3c6; }
}
```

Daraus folgt eine klare Entscheidungsregel:

- **Tokens ungelayert lassen**, wenn sie als unverrückbare Konstanten gedacht sind, die von überall gelesen und nirgends überschrieben werden. Das ist die Wahl, die auf jpkc.com getroffen wurde — der `:root`-Block steht dort bewusst vor und außerhalb der Layer-Deklaration.
- **Tokens layern**, wenn Themes, White-Labels oder Komponentenvarianten sie planmäßig überschreiben sollen — White-Label heißt: dasselbe Produkt läuft bei mehreren Kunden unter deren eigener Marke, gleiche Funktion, ausgetauschte Farben und Logos. Dann aber **konsequent**: Auch die Theme-Overrides und jeder `prefers-color-scheme`-Block müssen in Layer, sonst gewinnt der Zufall.

Der Fehler ist die Mischform. Ein Teil der Tokens gelayert, der andere nicht — das erzeugt genau die Sorte Bug, die sich nur beim Dark-Mode-Wechsel auf einer bestimmten Unterseite zeigt.

## Welche Layer-Reihenfolge? Ein konkreter Vorschlag

Es gibt keine kanonische Reihenfolge, aber es gibt eine, die sich bewährt hat und die im Kern der Logik von ITCSS folgt: **von allgemein nach speziell, von fremd nach eigen.**

```css
@layer overrides, vendor, reset, base, layout, components, states, utilities;
```

| Layer | Inhalt | Warum an dieser Stelle |
|---|---|---|
| `overrides` | Ausschließlich `!important`-Notbremsen gegen Fremd-CSS | Ganz vorne — bei `!important` ist das der stärkste Platz |
| `vendor` | Bootstrap, Widgets, Consent-Banner, alles Fremde | Früh, damit alles Eigene normal gewinnt |
| `reset` | Normalisierung, `box-sizing`, Margin-Reset | Grundlage für alles Eigene |
| `base` | Element-Defaults: Typografie, Links, Tabellen, Formulare | Baut auf dem Reset auf |
| `layout` | Raster, Container, Seitengerüst | Trägt Komponenten, wird von ihnen nicht überschrieben |
| `components` | Buttons, Karten, Navigation, alles Wiederverwendbare | Der Ort, an dem die meiste Arbeit passiert |
| `states` | `is-open`, `[disabled]`, `[aria-current]`, Fehlerzustände | Muss Komponenten schlagen, ohne spezifischer zu sein |
| `utilities` | Einzweck-Klassen: `.mt-0`, `.visually-hidden` | Letztes Wort im Markup — genau dafür sind sie da |

Vier Anmerkungen dazu:

**`states` als eigener Layer ist unterschätzt.** Der klassische Schmerz „`.card.is-active` überschreibt `.card__body .card__title` nicht" verschwindet damit ersatzlos.

**`responsive` gehört normalerweise *nicht* in einen eigenen Layer.** Bei jpkc.com ist es einer, weil dort alle `@media`-Blöcke historisch am Dateiende stehen und die Umstellung diesen Zustand abbilden sollte. Der sauberere Weg für neue Projekte ist, Media Queries *innerhalb* des Layers zu schreiben, zu dem sie inhaltlich gehören — ein responsives Button-Verhalten gehört zu `components`. Ein eigener `responsive`-Layer erzwingt sonst, dass jede Breakpoint-Regel jede Komponentenregel schlägt, auch wenn das gar nicht gemeint war.

**Weniger Layer sind besser als mehr.** Vier bis sechs sind für die meisten Projekte genug. Jeder zusätzliche Layer ist eine weitere Regel, die alle im Team im Kopf haben müssen.

**Ungelayertes CSS ist eine Entscheidung, kein Zustand.** Weil es alles schlägt, sollte es entweder gar nicht vorkommen oder eine bewusst dokumentierte Rolle haben (etwa: nur Design-Tokens).

## Debugging und Werkzeuge

**Chrome und Edge DevTools** zeigen Layer im Styles-Panel: Regeln aus einem Layer tragen ein `@layer <name>`-Etikett, und über die Schaltfläche *Toggle CSS layers view* neben dem Suchfeld bekommst du die vollständige Layer-Rangfolge des Elements — von der höchsten Priorität oben bis zur niedrigsten unten. Das ist beim Einführen von Layern das mit Abstand nützlichste Werkzeug, weil es die Frage „warum gewinnt diese Regel?" direkt beantwortet.

**Firefox DevTools** zeigen die Layer-Zugehörigkeit ebenfalls im Regel-Panel.

**Per JavaScript** lässt sich die Struktur prüfen: `CSSLayerStatementRule` (die Reihenfolge-Deklaration) und `CSSLayerBlockRule` (der Block) sind reguläre CSSOM-Objekte. Das CSSOM (CSS Object Model) ist der Objektbaum, als den der Browser jedes Stylesheet spiegelt — darüber liest JavaScript `document.styleSheets` und darin `cssRules`:

```js
for (const sheet of document.styleSheets) {
	for (const rule of sheet.cssRules) {
		if (rule instanceof CSSLayerStatementRule) console.log('Reihenfolge:', rule.nameList);
		if (rule instanceof CSSLayerBlockRule)     console.log('Block:', rule.name);
	}
}
```

Damit lässt sich in der CI prüfen — der automatischen Prüfstrecke, die bei jedem Commit baut und testet —, ob der gebaute Output die erwartete Layer-Reihenfolge hat — eine Zusicherung, die deutlich mehr wert ist als jeder visuelle Test, weil sie die *Ursache* prüft statt der Wirkung.

Für den Ernstfall muss die Schleife allerdings absteigen: So wie oben sieht sie nur die oberste Ebene und übersieht damit Layer-Blöcke innerhalb anderer Layer, innerhalb von `@media` sowie alles in importierten Stylesheets. Vollständig wird sie erst, wenn sie in `CSSGroupingRule.cssRules` und `CSSImportRule.styleSheet` hineinrekursiert. Anonyme Blöcke melden dabei einen leeren `name`.

## Browser-Support und Migration

`@layer` ist in allen Engines seit **März 2022** verfügbar und trägt inzwischen den Baseline-Status *Widely available*. Baseline ist die Support-Einstufung der WebDX Community Group, wie sie MDN, web.dev und Can I use anzeigen: *Newly available* heißt, das Feature ist in allen Kern-Browsern angekommen, *Widely available* dasselbe 30 Monate später.

| Browser | Ab Version |
|---|---|
| Chrome / Edge | 99 |
| Firefox | 97 |
| Safari | 15.4 |
| Opera | 86 |
| Samsung Internet | 18 |

Ein Punkt ist dabei kritisch und wird gern übersehen: **Es gibt keinen sinnvollen Fallback.** Ein Browser, der `@layer` nicht kennt, ignoriert nach den CSS-Fehlerbehandlungsregeln die At-Rule **samt ihrem gesamten Block**. Deine Buttons wären dort also nicht falsch priorisiert, sondern komplett ungestylt.

Eine Feature-Abfrage hilft dabei nicht weiter. Zwar gibt es seit Chrome 148 ein `@supports at-rule(@layer)`, aber genau dort beißt sich die Katze in den Schwanz: Jeder Browser, der `@layer` nicht kennt, kennt erst recht kein `at-rule()`. Ein Laufzeit-Polyfill existiert ebenfalls nicht — ein Polyfill ist Code, meist JavaScript, der eine fehlende Plattformfunktion nachbaut, damit sie auch ohne native Unterstützung nutzbar ist. Die Kaskade lässt sich so aber nicht nachbauen, sondern nur, indem man sämtliche Selektoren umschreibt. Genau das tut `@csstools/postcss-cascade-layers` (Teil von `postcss-preset-env`) zur **Build-Zeit**, indem es Selektoren mit wiederholten `:not(#\#)` auf die passende Spezifität aufpolstert.

Verschärfend kommt hinzu: In einem Browser ohne `@layer`-Unterstützung ist auch `@import url(x.css) layer(y)` ein **ungültiger** `@import` — das Stylesheet ist dort also nicht falsch einsortiert, sondern lädt überhaupt nicht.

Die praktische Antwort lautet trotzdem: Bei über vier Jahren Verfügbarkeit in allen Engines ist das für nahezu jedes Publikum unproblematisch — aber schau einmal in deine Analytics, bevor du umstellst, statt es anzunehmen.

## Häufige Fehler — die Checkliste

| Symptom | Ursache | Lösung |
|---|---|---|
| Layer-Reihenfolge wirkt nicht | Die `@layer a, b;`-Zeile steht nach den Blöcken | Reihenfolge-Deklaration in die erste Zeile |
| Fremd-CSS gewinnt trotz Layer | Es kommt aus einem `<link>` und ist ungelayert | Über ein Wrapper-Stylesheet importieren oder beim Build bündeln |
| Eigenes `!important` verliert | Der Layer des Frameworks liegt früher | Separaten `overrides`-Layer *vor* das Framework legen |
| Utility-Klassen wirken nicht | Eigenes CSS liegt ungelayert neben Tailwind | Eigenes CSS in `@layer components` einordnen |
| Dark Mode kippt gelayerte Tokens | Der `prefers-color-scheme`-Block ist ungelayert | Theme-Overrides in denselben Layer wie die Tokens |
| Layer-Reihenfolge ändert sich je Viewport | Ein Layer wird erstmals in einer `@media`-Bedingung angelegt | Alle Layer außerhalb von Bedingungsblöcken deklarieren |
| `@layer` fehlt im Build-Output | Der Minifier entfernt die At-Rules | Minifier prüfen und im Zweifel wechseln |
| Shadow-DOM-Styling reagiert nicht auf Layer | Der Kapselungskontext steht über den Layern | Styling-API der Komponente nutzen (Custom Properties, `::part()`) — die gehorcht dann wieder deiner Dokument-Layer-Reihenfolge |
| Regel gewinnt trotz letztem Layer nicht | Inline-Style, laufende Animation oder Transition | Layer greifen dort grundsätzlich nicht |

## FAQ

**Ersetzt `@layer` Methodiken wie BEM?**
Nein, es entlastet sie. BEM löst Namenskonflikte und macht Zugehörigkeit im Markup lesbar; `@layer` löst Prioritätskonflikte. Was `@layer` überflüssig macht, sind die *spezifitätsgetriebenen* Teile solcher Konventionen — das künstliche Verdoppeln von Klassen, das Hochzählen von Selektoren, die Regel „Utilities müssen ganz unten stehen".

**Kann ich `@layer` schrittweise einführen?**
Ja, und das ist der empfohlene Weg. Weil ungelayertes CSS alles Gelayerte schlägt, kannst du zuerst nur das Fremd-CSS in einen Layer ziehen. Dein gesamter Bestand bleibt unverändert stark, und du hast trotzdem sofort das Framework unter Kontrolle.

**Was passiert bei doppelten Layer-Namen in verschiedenen Dateien?**
Sie bezeichnen denselben Layer, sofern beide Dateien im selben Baum liegen. Das ist gewollt — so kannst du `@layer components { … }` über viele Dateien verteilen. Innerhalb von Shadow-Bäumen gilt das nicht: Dort ist jeder Baum ein eigener Namensraum.

**Sind Layer schlecht für die Performance?**
Nein. Die Messung an einer real inline eingebetteten Datei ergab +83 Byte pro Seite nach Minifizierung — je nach Seitengröße 0,06 bis 0,18 %; die Auswertung passiert einmal beim Erstellen der Stylesheet-Reihenfolge, nicht pro Element. Der einzige reale Performance-Aspekt ist das serielle Laden bei `@import`-Ketten — und das ist ein `@import`-Problem, kein Layer-Problem.

**Wie verhält sich `@layer` zu `@scope`?**
Die beiden ergänzen sich und sind unabhängig. `@layer` beantwortet „welche Schicht gewinnt", `@scope` beantwortet „auf welchen DOM-Bereich zielt diese Regel". In der Sortierreihenfolge ist die Scope-Nähe eine **eigene Stufe direkt nach der Spezifität** — Level 5 der Spezifikation kennt sie noch nicht, Level 6 fügt sie ein. Sie liegt damit weit hinter der Layer-Entscheidung, und eine nähere Scope-Wurzel erhöht **nicht** die Spezifität: Sie entscheidet erst, wenn die Spezifität gleich ist. Ein `@scope`-Block darf in einem Layer stehen und umgekehrt.

**Was mache ich mit `!important` im Bestand?**
Inventarisieren, bevor du Layer einführst. Jedes `!important` verhält sich nach der Umstellung anders als vorher, weil es der Layer-Reihenfolge nun rückwärts folgt. Der Idealfall ist, die meisten davon zu löschen — genau dafür ist `@layer` gedacht. Was übrig bleibt, gehört in einen bewusst platzierten `overrides`-Layer statt verstreut über die Codebasis.

## Fazit

Cascade Layers sind eine der wenigen CSS-Neuerungen, die man an einem Nachmittag versteht und trotzdem jahrelang nutzt. Der Kern ist eine Zeile:

```css
@layer reset, base, components, utilities;
```

Ab diesem Moment ist die Frage „welche Regel gewinnt?" nicht mehr eine Frage an den Selektor, sondern an die Architektur. Und das ist die Frage, die du eigentlich stellen wolltest.

Die drei Punkte, die aus der Praxis hängen bleiben:

1. **Ungelayertes CSS schlägt alles Gelayerte.** Diese eine Regel erklärt gefühlt die Hälfte aller Überraschungen — und macht gleichzeitig die schrittweise Migration erst möglich.
2. **`!important` dreht die Reihenfolge um.** Wer ein Framework in den ersten Layer legt, macht dessen `!important`-Regeln unbesiegbar, wenn er nicht davor noch einen Override-Layer einplant.
3. **Über die Dokumentgrenze hinweg helfen Layer nicht.** Der Kapselungskontext steht in der Kaskade über ihnen, deshalb erreichst du eine Komponente nicht per Layer. Custom Properties und `::part()` bleiben die Styling-API — und die gehorcht deiner Layer-Reihenfolge dann wieder ganz normal.

Wenn du es ausprobieren willst, ohne ein Projekt anzufassen: Der [Playground](https://www.jpkc.com/db/tools/playground/) hat einen HTML/CSS-Editor mit Live-Vorschau — die Beispiele aus diesem Artikel lassen sich dort direkt einfügen und verändern.

## Glossar

Begriffe, die im Text vorkommen und dort den Satz überladen hätten. Wer sie kennt, überspringt die Tabelle.

| Begriff | Bedeutung |
|---|---|
| `all: unset` | Die Kurzschrift `all` setzt alle Eigenschaften außer `direction` und `unicode-bidi` zurück — hier entfernt sie das Browser-Styling des Buttons. |
| Attribut-Selektor | Eckige Klammern treffen Elemente mit einem Attribut: `[disabled]` jedes, das es trägt, `:host([disabled])` den Host nur dann. |
| BEM-Notation | Doppelunterstrich trennt Block von Element (`.card__body` ist der Body-Teil der Card); das `is-`-Präfix markiert Zustände (aus SMACSS). |
| Breakpoint | Viewport-Schwelle, ab der das Layout umschaltet. |
| `display` / `box-sizing` | Ohne eigenes `display` ist ein Custom Element `inline`, `width` und `height` greifen nicht; `box-sizing: border-box` rechnet Padding und Rahmen in die Breite ein. |
| Distribution (`dist/`) | Das fertig kompilierte CSS eines Frameworks — genau die Datei, die du per `@import` layern kannst; die Sass-Quellen liegen daneben in `scss/`. |
| `@import "tailwindcss"` | Der nackte Paketname statt `url()` wird vom Build-Werkzeug aufgelöst und durch Tailwinds CSS ersetzt — im Browser wäre er eine relative URL ins Leere. |
| `inverted-colors` | Barrierefreiheits-Media-Feature: erkennt vom Betriebssystem oder Browser invertierte Farben (`none`/`inverted`); praktisch unterstützt nur Safari es. |
| Mixin | Benannter Sass-Baustein, den du per `@include` einsetzt; die Definition allein gibt kein CSS aus. |
| `platforms` / `buildPath` / `transformGroup` | Style Dictionary: `platforms` listet die Ziele, `buildPath` ist das Ausgabeverzeichnis, `transformGroup: 'css'` bündelt die Umwandlungen — darunter die Kebab-Case-Namen. |
| Print-Stylesheet | Der `@media print`-Block für die Druckausgabe. |
| Pseudo-Klasse / Pseudo-Element | Ein Doppelpunkt = Zustand des Elements (`:target`, `:focus-visible`), zwei Doppelpunkte = gerenderter Teil ohne eigenen DOM-Knoten (`::selection`); Ausnahme ist das alte `:before`. |
| Reset | Vereinheitlicht Browser-Voreinstellungen und setzt eigene Basiswerte — meist `box-sizing: border-box` und genullte Außenabstände; Tailwinds Variante heißt Preflight und baut auf modern-normalize auf. |
| Selektor | Bestimmt, welche Elemente eine Regel trifft: Leerzeichen heißt „irgendwo darin" (`.sidebar a`), kein Leerzeichen „dasselbe Element" (`.btn.plain`). |
| `<slot>` | Platzhalter im Shadow-Baum, an dem die Kindknoten erscheinen, die im Dokument zwischen den Tags des Elements stehen. |
| `.visually-hidden` | Blendet Text optisch aus, lässt ihn aber für Screenreader lesbar. |

## Weiterführende Links

- [`@layer` — MDN](https://developer.mozilla.org/de/docs/Web/CSS/@layer) — Referenz zur At-Rule
- [Cascade Layers — MDN Lernbereich](https://developer.mozilla.org/de/docs/Learn_web_development/Core/Styling_basics/Cascade_layers) — ausführliche Einführung mit Beispielen
- [`revert-layer` — MDN](https://developer.mozilla.org/de/docs/Web/CSS/revert-layer) — das Schlüsselwort zum Zurückrollen
- [CSS Cascading and Inheritance Level 5 — W3C](https://www.w3.org/TR/css-cascade-5/) — die Spezifikation, inklusive der vollständigen Sortierreihenfolge (englisch)
- [Design Tokens Format Module](https://www.designtokens.org/TR/drafts/format/) — Spezifikation mit den Regeln zu `$extensions` (englisch)
- [Style Dictionary](https://styledictionary.com/) — Token-Compiler, Basis des Generator-Beispiels (englisch)
- [Bootstrap: Sass-Optionen](https://getbootstrap.com/docs/5.3/customize/options/) — inklusive `$enable-important-utilities` (englisch)
- [Tailwind CSS v4](https://tailwindcss.com/blog/tailwindcss-v4) — Ankündigung mit den nativen Cascade Layers (englisch)

**Aus diesem Blog:**

- [Core Web Vitals: Was wirklich zählt](https://www.jpkc.com/db/blog/core-web-vitals/) — warum die Größe und Ladereihenfolge deines CSS messbar wird
- [Technisches SEO](https://www.jpkc.com/db/blog/technical-seo/) — Rendering, Ressourcen und Crawling im Zusammenhang

