Lesson 19
@scope - Proximity-Based Styling
You've learned how to control which styles win via layers (Lesson 11) and specificity (Lesson 14). @scope adds a new dimension: proximity. Styles from a closer scope root beat styles from a farther one - regardless of source order. It also lets you define where styles stop applying, creating true component boundaries in CSS.
The mental model
@scope does two things:
- Limits where styles apply - styles inside
@scope (.card)only match elements inside.card. Like a built-in descendant selector, but with a defined boundary. - Proximity wins - when two scopes both match an element, the closer ancestor scope root wins. No specificity battle needed.
How it fits in the cascade: Scope proximity is checked AFTER layers and specificity, but BEFORE source order. So if two declarations have the same origin, layer, and specificity - the one from the closer scope root wins.
Cascade resolution: origin → inline → layers → specificity → SCOPE PROXIMITY → source order
Basic @scope - scoping styles to a subtree
The blue text and orange title only apply inside .scope-card - the paragraph outside is unaffected
I'm outside the scope - normal styling.
I'm inside .scope-card - scoped blue styling applies to me.
<!-- HTML -->
<p>I'm outside the scope — normal styling.</p>
<div class="card">
<h3 class="title">Card Title</h3>
<p>I'm inside — scoped styling applies.</p>
</div>
/* CSS */
@scope (.card) {
p { color: blue; font-weight: 500; }
.title { color: coral; }
}
/* These rules ONLY apply to p and .title elements
that are descendants of .card. Elements outside .card
are completely unaffected — no specificity needed to exclude them. */
Scope limits - where styles STOP applying
The killer feature: you can define a lower boundary. Styles apply from the scope root DOWN TO (but not including) the limit:
Blue background applies between .outer and .inner - the paragraph inside .inner is unaffected
I'm between outer and inner - scoped style applies (blue background).
I'm inside .inner - the scope STOPS here. No blue background.
<!-- HTML -->
<div class="outer">
<p>I get styled (between root and limit).</p>
<div class="inner">
<p>I do NOT get styled (past the limit).</p>
</div>
</div>
/* CSS — scope FROM .outer, STOP AT .inner */
@scope (.outer) to (.inner) {
p { background: lightblue; padding: 0.5rem; border-radius: 4px; }
}
/* "to" defines the lower boundary.
Elements AT or INSIDE .inner are excluded from the scope.
This creates a "donut" — styles apply in between. */
Why scope limits matter: They solve the "nested component" problem. If you have a .card inside a .card, styles for the outer card's children shouldn't bleed into the inner card. With @scope (.card) to (.card), they stop at the boundary of any nested card.
Proximity - closer scope root wins
When an element is inside multiple overlapping scopes, the one with the closer ancestor scope root wins:
The badge is inside both .theme-blue and .theme-coral - the closer one (.theme-coral) wins
theme-blue scope
Blue badge (closest scope is blue)theme-coral scope (nested inside blue)
Coral badge (closest scope is coral - wins!)<!-- HTML -->
<div class="theme-blue">
<span class="badge">Blue (closest scope is blue)</span>
<div class="theme-coral">
<span class="badge">Coral (closest scope is coral — wins!)</span>
</div>
</div>
/* CSS — two scopes with the same specificity */
@scope (.theme-blue) {
.badge { background: blue; color: white; }
}
@scope (.theme-coral) {
.badge { background: coral; color: white; }
}
/* Both rules match the inner badge (it's inside both scopes).
Same specificity. Same layer.
Proximity wins: .theme-coral is the CLOSER ancestor → coral wins.
Without @scope, source order would decide (coral last → coral wins by coincidence).
With @scope, proximity ALWAYS wins — order doesn't matter. */
The mental model for proximity: "Which scope root is the shortest DOM walk from this element?" That one wins. It's like CSS specificity, but measured in DOM distance rather than selector weight.
@scope vs other scoping approaches
| Approach | How it works | Limitations |
|---|---|---|
| BEM naming | Convention-based - unique class names prevent clashes | Manual, verbose, no lower boundary |
| Angular ViewEncapsulation | Compiler adds unique attributes to scope component styles | Framework-specific, can't define lower boundaries, no proximity |
| Shadow DOM | Hard encapsulation boundary - styles can't leak in or out | Too strict for many use cases, hard to theme from outside |
@scope |
Native CSS scoping with upper AND lower boundaries + proximity | Still relatively new (Chrome 118+, no Firefox yet) |
The :scope selector
Inside @scope, the :scope pseudo-class refers to the scope root element itself:
@scope (.card) {
:scope {
/* Styles the .card element itself (the root) */
border: 1px solid gray;
border-radius: 8px;
}
:scope > .title {
/* Direct children of the scope root */
font-weight: 600;
}
p {
/* Any p descendant within the scope */
color: #555;
}
}
Practical patterns
Component isolation - nested cards don't leak
Outer card has scoped styles that stop at nested cards
Outer card content (styled by outer scope)
Inner card content (NOT styled by outer scope - boundary stops it)
<!-- HTML -->
<div class="card">
<p>Outer card content</p>
<div class="card">
<p>Inner card — outer styles DON'T reach here</p>
</div>
</div>
/* CSS — scope stops at nested .card boundaries */
@scope (.card) to (.card) {
p { color: blue; font-weight: 500; }
}
/* The inner .card gets its own scope instance.
Outer card's p styles don't leak into inner card. */
Theme regions without custom properties
<!-- HTML -->
<section class="light-region">
<p>Light themed content</p>
</section>
<section class="dark-region">
<p>Dark themed content</p>
</section>
/* CSS */
@scope (.light-region) {
:scope { background: white; }
p { color: #1a1a2e; }
}
@scope (.dark-region) {
:scope { background: #1a1a2e; }
p { color: #f0f0f0; }
}
Browser support
@scope: Chrome 118+, Edge 118+, Safari 17.4+. Firefox: not yet supported (as of mid-2025). Progressive enhancement - without support, styles simply apply globally (the scope limits are ignored).
Retrieval check
Question 1
What does the "to" clause in @scope (.card) to (.slot) define?
Question 2
An element is inside both @scope(.blue) and @scope(.red) - same specificity, same layer. Which wins?
Question 3
Where does scope proximity sit in the cascade resolution order?
Question 4
What does :scope refer to inside @scope (.card)?
Question 5
How does @scope (.card) to (.card) prevent style leakage into nested cards?