Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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

ApproachHow it limits stylesSpecificity
BEMunique class names on every elementone class
Nestingthe parent selector before the child selectorgrows with every level
@scopea scope with a root and a lower boundarythe 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.

Thanks for reading!

Donate to the site — we work hard for you.