Wer Komponenten mit eigenem CSS baut, kennt das Muster: Eine Regel für die Überschrift in der Karte trifft plötzlich auch die Überschrift in einer anderen Komponente, die zufällig in der Karte steckt. Die üblichen Antworten sind längere Selektoren, Namenskonventionen wie BEM oder Werkzeuge wie CSS Modules, die Klassennamen umschreiben. @scope löst dasselbe Problem im Browser. Die Regel steckt einen Bereich ab, der an einem Element beginnt und auf Wunsch an einem anderen endet, und Selektoren darin greifen nur innerhalb dieser Grenzen. Treffen zwei Scopes auf dasselbe Element, gewinnt bei gleicher Spezifität der nähere. Chrome und Edge kennen @scope seit Version 118, Firefox seit 146 im Dezember 2025, und seit März 2026 führt MDN es als Baseline. Dieser Beitrag zeigt die Syntax, den Donut-Scope, die Rolle der Nähe in der Kaskade, wo @scope neben Tailwind hilft und wo nicht.
- @scope (.card) { ... } wendet die Regeln darin nur auf Elemente innerhalb von .card an. Einfache Selektoren wie h3 oder img brauchen dafür keine längere Kette und bekommen keine zusätzliche Spezifität.
- Mit to (...) bekommt der Bereich eine Untergrenze. @scope (.card) to (.card-slot) lässt alles in .card-slot unberührt, etwa eingesetzte Inhalte oder andere Komponenten. Das nennt man Donut-Scope.
- Treffen zwei Scopes mit gleicher Spezifität auf dasselbe Element, gewinnt der, dessen Wurzel näher liegt. Die Nähe kommt in der Kaskade nach der Spezifität und vor der Reihenfolge im Stylesheet.
- @scope isoliert nichts. Vererbte Eigenschaften wie color laufen weiter über die Grenze, und ein Browser ohne Unterstützung verwirft den ganzen Block. Für Pflicht-Styles braucht es deshalb eine Fassung, die auch ohne trägt.
Warum überschreiben sich Komponenten-Styles gegenseitig?
Weil ein Selektor in CSS nur sagt, wo etwas anfängt, aber nicht, wo es aufhört. .card h3 trifft jede Überschrift dritter Ordnung irgendwo innerhalb der Karte, auch die in einer Komponente, die in der Karte steckt und ihre eigenen Regeln mitbringt. Welche Regel gewinnt, entscheidet dann die Spezifität oder die Reihenfolge im Stylesheet, also etwas, das mit der Absicht nichts zu tun hat.
Die bekannten Antworten arbeiten alle um das Problem herum. BEM verlangt, dass jedes Element eine eigene Klasse mit dem Namen der Komponente trägt, damit Selektoren nie verschachtelt werden müssen. CSS Modules und ähnliche Werkzeuge schreiben Klassennamen beim Bauen um, sodass sie eindeutig werden. Beides funktioniert, beides verschiebt die Arbeit in Namen und Werkzeuge.
Was fehlte, war eine Grenze im CSS selbst: diese Regeln gelten hier, und dort nicht mehr. Genau das ist @scope.
Ein Selektor sagt, wo es anfängt. @scope sagt auch, wo es aufhört.
Wie funktioniert @scope?
Man nennt in Klammern die Wurzel des Bereichs, und alle Regeln im Block gelten nur für die Wurzel und ihre Nachfahren. Innerhalb des Blocks schreibt man einfache Selektoren wie h3, p oder img, ohne die Klasse der Komponente davor. Die Wurzel selbst erreicht man mit :scope.
Die Wurzel zählt bei der Spezifität nicht mit. Ein h3 im Scope ist so stark wie ein h3 ohne, MDN beschreibt das, als stünde ein :where(:scope) davor. Nur wer :scope ausdrücklich schreibt, bekommt dessen Gewicht dazu, das einer Klasse. Damit bleiben Selektoren kurz und schwach, und eine bewusst gesetzte Klasse am Element setzt sich weiter durch.
Es gibt auch eine Form ohne Klammern. Ein style-Element mit @scope ohne Wurzel gilt für das Elternelement des style-Elements. Das ist praktisch für Inhalte, die mitsamt ihrem CSS an einer Stelle eingesetzt werden, etwa eine einzelne Landingpage-Section aus einem CMS.
@scope (.card) { :scope { border-radius: 16px; padding: 1.5rem; } h3 { font-size: 1.25rem; } img { border-radius: 12px; }}
Was ist ein Donut-Scope?
Ein Bereich mit Loch: Er beginnt an der Wurzel und endet an einer Untergrenze, die man mit to angibt. @scope (.card) to (.card-slot) gilt für alles in der Karte, außer für das, was in .card-slot steckt. Die Wurzel gehört dazu, die Untergrenze selbst nicht.
Das ist genau der Fall vom Anfang. Eine Karte, in deren Inhalt andere Komponenten eingesetzt werden, kann ihre Überschriften und Bilder gestalten, ohne die Überschriften und Bilder der eingesetzten Komponenten zu berühren. Die Grenze steht im CSS, nicht in den Namen, und die eingesetzte Komponente muss nichts davon wissen.
Die Untergrenze darf eine ganze Liste sein. Wer mehrere Komponenten kennt, die nie von außen gestaltet werden sollen, schreibt sie mit Komma getrennt in die zweite Klammer.
@scope (.card) to (.card-slot, .media-player) { img { aspect-ratio: 4 / 3; object-fit: cover; }}
- .card img trifft auch die Bilder in jeder Komponente, die in der Karte steckt.
- Um das zu verhindern, kommt ein :not() dazu, und die Spezifität steigt mit.
- Die eingesetzte Komponente braucht danach selbst stärkere Selektoren, um sich durchzusetzen.
- img im Scope trifft nur die Bilder bis zur Untergrenze.
- Die Spezifität bleibt die eines einfachen Elements.
- Die eingesetzte Komponente gestaltet sich selbst, ohne gegen die Karte zu kämpfen.
Welcher Scope gewinnt, wenn zwei auf dasselbe Element treffen?
Bei gleicher Spezifität der, dessen Wurzel näher am Element liegt. Diese Nähe ist eine eigene Stufe in der Kaskade. Sie kommt nach Wichtigkeit, Ebenen und Spezifität, aber vor der Reihenfolge im Stylesheet. Bisher entschied an dieser Stelle die Reihenfolge, also die Frage, welche Regel zuletzt geschrieben wurde.
Das bekannteste Beispiel sind verschachtelte Farbschemata. Eine helle Fläche in einem dunklen Bereich, darin ein Link: Mit gewöhnlichen Selektoren wie .dark a und .light a gewinnt der, der weiter unten im Stylesheet steht, egal wie die Elemente verschachtelt sind. Mit zwei Scopes gewinnt der nähere, und der Link in der hellen Fläche wird hell gestaltet, auch wenn diese Fläche selbst in einem dunklen Bereich liegt.
Wichtig ist die Reihenfolge der Stufen. Nähe schlägt nur die Reihenfolge, nicht die Spezifität. Ein Selektor mit einer Klasse mehr gewinnt weiter, auch aus dem ferneren Scope. Wer sich auf die Nähe verlassen will, hält die Selektoren in beiden Scopes gleich stark.
@scope (.light) { a { color: #1d4ed8; }} @scope (.dark) { a { color: #93c5fd; }} <section class="dark"> <div class="light"> <a href="/pricing">See pricing</a> </div></section>
Wo hilft @scope neben Tailwind?
Dort, wo man keine Klassen an jedes Element schreiben kann. Für eine Komponente, deren Markup man selbst schreibt, braucht es @scope meist nicht, denn eine Klasse wie text-lg trifft genau das Element, an dem sie steht. Das Problem der zu weit greifenden Selektoren entsteht dort gar nicht.
Anders ist es bei Inhalten, die von außen kommen. Fließtext aus einem CMS oder aus Markdown bringt Überschriften, Listen und Bilder ohne Klassen mit. Tailwind bietet dafür Varianten wie [&_h2]:text-2xl am Container oder ein Plugin für Fließtext. Ein kleines Stylesheet mit @scope (.article-body) to (.embed) ist oft lesbarer, und die Untergrenze schützt eingebettete Komponenten wie Code-Blöcke oder Videos vor den Regeln für den Text.
Der zweite Fall ist Markup aus Bibliotheken, deren Klassen man nicht ändern kann. Statt deren Klassen zu überschreiben, steckt man einen Scope um den Container und gestaltet die Elemente darin mit einfachen Selektoren, ohne dass die Regeln an anderer Stelle der Seite etwas anrichten.
@scope (.article-body) to (.embed) { h2 { margin-block: 2.5rem 1rem; font-size: var(--text-2xl); } a { text-decoration-thickness: 1px; text-underline-offset: 3px; }}
Wo stößt @scope an Grenzen?
An drei Stellen, und alle drei hängen damit zusammen, dass @scope eine Regel im Stylesheet ist und keine Trennung im Dokument. Die erste ist die Vererbung. Eigenschaften wie color oder font-family werden weiter an Kinder vererbt, auch über die Untergrenze hinweg. Der Donut-Scope verhindert nur, dass Selektoren dort greifen, nicht, dass vererbte Werte dort ankommen.
Die zweite ist das Verschachtelungszeichen &. MDN weist darauf hin, dass Browser und Versionen es innerhalb von @scope unterschiedlich behandeln, und die Chrome-Dokumentation rechnet ihm die Spezifität der Wurzel zu, während einfache Selektoren keine bekommen. Wer verlässliche Ergebnisse will, schreibt einfache Selektoren oder :scope.
Die dritte ist der Rückfall. Ein Browser ohne @scope verwirft den ganzen Block. Anders als bei einer neuen Eigenschaft, die einfach übersprungen wird, fehlen dann alle Regeln darin. Seit März 2026 ist das in aktuellen Browsern kein Thema mehr, für Seiten mit vielen Besuchern auf alten Geräten aber schon. Dort gehören die tragenden Regeln in gewöhnliche Klassen-Selektoren und die Feinheiten in den Scope.
- Vererbung läuft weiter. color, font-family und andere vererbte Werte erreichen auch Elemente hinter der Untergrenze.
- & ist unzuverlässig. Innerhalb von @scope einfache Selektoren oder :scope schreiben, nicht &.
- Ohne Unterstützung fehlt alles. Der ganze Block wird verworfen. Tragende Regeln deshalb außerhalb des Scopes schreiben, wenn alte Browser zählen.
@scope begrenzt, wo Regeln greifen. Was vererbt wird, begrenzt es nicht.






