Über diese Website

Dieser Kasten steht auf jeder Seite in der linken Spalte. Was darin steht, änderst du in der Konfiguration unter Plugins — oder du schaltest ihn dort ab.

Über dieses Handbuch

Dieses Handbuch gehört zur Vorlage Example für QuartzControl. Es erklärt die Vorlage und führt zugleich alles vor, was sie kann.

Der Text hier kommt aus einer Markdown-Datei, nicht aus der Konfiguration.

CSS @scope

CSS @scope

Kurz erklärt

@scope begrenzt, wo Selektoren greifen dürfen. Man nennt einen Startpunkt im DOM (Scope Root) und optional eine Grenze (Scope Limit). Die Regeln darin treffen nur Elemente dazwischen.1

@scope (.artikel) to (figure) {
  img { border: 5px solid; }
}

↗ Beispiel in neuem Tab öffnen

Das trifft alle Bilder im .artikel, aber keine, die in einer figure stecken.

Hintergrund

Wer ein ganz bestimmtes Element treffen will, hat zwei schlechte Möglichkeiten:2

  • Einen langen Selektor wie .karte > .inhalt > img.hero. Der ist sehr spezifisch, lässt sich schwer überschreiben und bricht, sobald sich das HTML ändert.
  • Einen kurzen wie img. Der trifft dann aber alle Bilder auf der Seite.

Bisherige Auswege waren Namenskonventionen wie BEM (karte__bild--hero), generierte Klassennamen aus JavaScript-Werkzeugen oder Utility-Klassen direkt im HTML. @scope löst das Problem in CSS selbst: kurze Selektoren, die trotzdem nur in einem Bereich wirken.

Wichtige Aspekte

Root und Limit

@scope (.karte) { … }                /* alles in .karte, .karte selbst eingeschlossen */
@scope (.karte) to (.inhalt) { … }   /* .karte bis vor .inhalt */

Die zweite Form heißt Donut Scope: ein Bereich mit einem Loch darin. Die obere Grenze gehört dazu, die untere nicht. Wer das ändern will, hängt > * an: to (.inhalt > *) schließt .inhalt selbst mit ein, (.karte > *) schließt die Karte selbst aus.1

Root und Limit dürfen Selektorlisten sein, @scope (.hero, .teaser) to (figure) legt also zwei Bereiche auf einmal fest.1

:scope – das Root-Element selbst

Innerhalb von @scope meint :scope das Element, an dem der Bereich beginnt:1

@scope (.karte) {
  :scope { padding: 1rem; border: 1px solid; }   /* die Karte selbst */
  img    { border-radius: 0.5rem; }              /* Bilder darin */
}

Aus dem Bereich heraus kommt man nicht. MDN nennt :scope + p deshalb ungültig.1 In Chrome 155 wird die Regel zwar eingelesen, trifft aber nichts.

Direkt im HTML

Ohne Klammerangabe in einem <style>-Element gilt @scope für das Elternelement des <style>:1

<div class="karte">
  <style>
    @scope { img { border-radius: 0.5rem; } }
  </style>
  <img src="…" alt="">
</div>

↗ Beispiel in neuem Tab öffnen

Das ist praktisch für Komponenten, die ihr CSS gleich mitbringen, etwa aus einem Template.

Spezifität

Der Selektor in der Klammer zählt nicht mit. In @scope (#sidebar) { img { … } } hat img weiterhin nur die Spezifität eines Elements (0-0-1).2

  • Selektoren ohne Präfix und & zählen laut MDN so, als stünde :where(:scope) davor, also gar nicht.1
  • :scope zählt wie eine Klasse (0-1-0).1

Zu & gibt es einen Widerspruch in den Quellen: Der Chrome-Artikel von 2023 schreibt, & werde zu :is(<Root-Selektor>) und übernehme dessen Spezifität, bei @scope (#sidebar) also die einer ID.2 MDN warnt, dass das je nach Browser und Version unterschiedlich ist.1 In Chrome 155 zählt & nicht mit: @scope (#x) { & p { … } } verliert gegen .x p. Der Chrome-Artikel ist in diesem Punkt also veraltet. Firefox und Safari habe ich nicht geprüft.

Wer im Scope bewusst mehr Gewicht braucht, schreibt :scope aus.

Scoping Proximity: der nähere Bereich gewinnt

@scope bringt ein neues Kriterium in die Kaskade. Wenn zwei Regeln gleich spezifisch sind, gewinnt die, deren Scope Root im DOM näher am Element liegt, unabhängig von der Reihenfolge im Stylesheet.1

Klassisches Beispiel sind verschachtelte Farbthemen:

<div class="hell">
  <div class="dunkel">
    <div class="hell">
      <p>Soll dunkel auf hell sein</p>
    </div>
  </div>
</div>
/* Ohne @scope: .dunkel p steht später und gewinnt – falsch */
.hell p   { color: black; }
.dunkel p { color: white; }
 
/* Mit @scope: das nächste Theme gewinnt – richtig */
@scope (.hell)   { p { color: black; } }
@scope (.dunkel) { p { color: white; } }

↗ Beispiel in neuem Tab öffnen

Der innere Absatz ist nur eine Ebene von .hell entfernt, aber zwei von .dunkel. Also gilt Schwarz.

Die Proximity steht in der Rangfolge nach !important, Cascade Layers und Spezifität und vor der Reihenfolge im Quelltext.12

flowchart LR
  A["Herkunft und !important"] --> B["Cascade Layers"]
  B --> C["Spezifität"]
  C --> D["Scoping Proximity"]
  D --> E["Reihenfolge im Quelltext"]

Kein Schutzwall gegen Vererbung

@scope begrenzt, welche Elemente die Selektoren treffen. Es verhindert nicht, dass sich Werte vererben. Setzt man color am Scope Root, erbt auch das „Loch“ im Donut diese Farbe.12

@scope (.karte) to (.inhalt) {
  :scope { color: hotpink; }   /* .inhalt und alles darin wird trotzdem pink */
}

↗ Beispiel in neuem Tab öffnen

Wer das Loch wirklich abschirmen will, muss dort die vererbten Eigenschaften selbst wieder setzen.

Beispiele

Komponente mit eigenem Inhaltsbereich, in den fremdes HTML kommt (etwa aus einem CMS):

@scope (.karte) to (.karte-inhalt) {
  :scope { display: grid; gap: 1rem; }
  h2     { font-size: 1.25rem; }
  a      { color: var(--akzent); }
}

Überschriften und Links in .karte-inhalt bleiben unberührt, egal wie das HTML dort aussieht.

Eigene Notizen / Einordnung

@scope ersetzt vor allem Namenskonventionen: Statt karte__titel schreibt man h2 im Scope der Karte. Ob das lesbarer ist, hängt vom Projekt ab. Bei Komponenten mit fremdem Inhalt, wie im Beispiel oben, ist das Donut-Limit ein echter Gewinn, den es vorher in CSS nicht gab.

Abgrenzung zu verwandten Werkzeugen:

  • CSS-Nesting schreibt Selektoren kürzer, begrenzt aber nichts. .karte { img { … } } trifft auch Bilder in verschachtelten Unterkomponenten.
  • Cascade Layers (@layer) regeln, welche Gruppe von Regeln gewinnt. @scope regelt, wo Regeln greifen. Beides lässt sich kombinieren.
  • :where() (siehe CSS-Pseudoklassen not(), is() und where()) macht Selektoren gewichtslos. Innerhalb von @scope ist das bei Selektoren ohne Präfix ohnehin der Fall.

Zurückhaltung beim Einsatz: @scope ist erst seit März 2026 in allen aktuellen Browsern verfügbar. Ältere Browser ignorieren den ganzen Block, und dann fehlen die Styles komplett. Für wichtige Styles braucht es deshalb vorerst eine Grundversion außerhalb von @scope.

Browsersupport

Laut web-features 3.43.0 (abgefragt 2026-10-18): Baseline newly available seit 2026-03-24 (Chrome/Edge 143, Firefox 146, Safari 26.4). Widely available wird es demnach frühestens im September 2028.

Siehe auch

Quellen

Eigene Ergänzungen: der Browsertest zur Spezifität von &, das Kaskaden-Diagramm (aus den Angaben beider Quellen), das CMS-Beispiel, die Abgrenzung zu Nesting, Layern und :where() sowie der Hinweis zum Fallback. Alle Codebeispiele dieser Notiz habe ich am 2026-10-18 in Chrome 155 nachgestellt, Firefox und Safari nicht.

Footnotes

  1. scope At-Regel (MDN), Abschnitte „Description“, „:scope pseudo-class within @scope blocks“, „Specificity in @scope“, „How @scope conflicts are resolved“, Stand 2026-04-20. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12

  2. Limit the reach of your selectors with the CSS scope at-rule (Chrome for Developers), Abschnitte „Introducing @scope“, „@scope and specificity“, „@scope in the cascade“, „Selector isolation, not style isolation“, 2023-10-04. ↩ ↩2 ↩3 ↩4 ↩5

Weiterlesen

Diese Seite gehört zu gui-test — der Beispielvorlage für QuartzControl. Ein Überblick über alle Bereiche steht auf der Startseite.

Auf einem schmalen Bildschirm ist die Navigation oben eingeklappt.