Style scope: @scope
The selector h3 finds every heading on the page. To make a style apply only inside a card, people usually write .card h3 or give the heading its own BEM class. The @scope rule offers a third way: it sets a scope — a part of the page inside which the rules work.
The first scope
The parentheses after @scope hold the scope root — the element inside which the styles apply. The rules inside the curly braces apply only to its descendants:
<style>
@scope (.card) {
h3 { margin: 0; color: rebeccapurple; }
}
</style>
<div class="card">
<h3>Anna — inside the card</h3>
</div>
<h3>A heading outside</h3>Only the heading in the card turned purple. Inside a scope you can write short tag selectors without fear of hitting the rest of the page.
The :scope pseudo-class
To style the scope root itself, use :scope:
<style>
@scope (.card) {
:scope { padding: 12px; border-radius: 8px; background: lavender; }
h3 { margin: 0 0 4px; }
p { margin: 0; color: dimgray; }
}
</style>
<div class="card">
<h3>Anna</h3>
<p>Front-end developer</p>
</div>All component styles are in one block, and the nested elements have no classes at all.
The lower boundary: to
After to you say where the scope ends. The styles do not go inside that element. The result is a scope "with a hole" — it is called a donut scope. This is how a card wrapper is styled without touching the text put inside it:
<style>
@scope (.card) to (.content) {
p { margin: 0; font-size: 13px; color: gray; }
}
.card { padding: 12px; background: whitesmoke; }
</style>
<div class="card">
<p>The card caption — gray and small</p>
<div class="content">
<p>The article text inside .content stayed regular.</p>
</div>
</div>The nearest root wins
When two scopes compete for one element, with equal specificity the one whose root is closer to the element wins. The order of rules in the file no longer matters. This is handy for nested themes:
<style>
.dark { padding: 12px; background: #333; color: white; }
.light { padding: 12px; margin-top: 8px; background: white; color: black; }
@scope (.light) { a { color: darkblue; } }
@scope (.dark) { a { color: gold; } }
</style>
<div class="dark">
<a href="#">A link in the dark theme</a>
<div class="light">
<a href="#">A link in the light theme inside the dark one</a>
</div>
</div>The rule for .dark comes last, but the lower link is dark blue: the .light root is closer to it. With regular selectors .light a and .dark a, the rule written lower would win.
@scope, BEM and nesting
| Approach | How it limits styles | Specificity |
|---|---|---|
| BEM | unique class names on every element | one class |
| Nesting | the parent selector before the child selector | grows with every level |
@scope | a scope with a root and a lower boundary | the root adds no weight |
BEM from the architecture chapter works everywhere and remains a reliable choice. @scope is useful when the markup cannot be covered in classes — for example, text from an article editor.
Support
@scope works in Chrome and Edge since 2023 and in Safari since 2024. Check the state of support in Firefox on caniuse.com. A browser without support skips the whole block, so do not rely on @scope alone for important styles yet.
Common mistake
Thinking that @scope isolates a component from other styles. It only limits its own rules, and the page's general styles still apply inside:
<style>
p { color: crimson; }
@scope (.card) {
h3 { color: seagreen; }
}
</style>
<div class="card">
<h3>The card heading</h3>
<p>The paragraph is still red — the p rule applies everywhere.</p>
</div>Full isolation comes only from web components with a shadow DOM; @scope is a way to aim your own styles, not to shield yourself from others.