How to scope CSS styles to specific document subtrees with @scope
Using scope boundaries with the "to" keyword
Understanding proximity-based cascade resolution
Component isolation without Shadow DOM
Nesting and overlapping scopes
Differences between @scope and other scoping methods
Best practices for scope-based architecture
When to use @scope vs. other CSS organization techniques
Introduction to CSS @scope
The @scope at-rule allows you to scope CSS styles to a specific subtree
of your document. This means styles only apply within a defined container, preventing conflicts and
making components truly portable. It's perfect for component-based architectures and avoiding the
global nature of traditional CSS.
Before @scope, developers used naming conventions (BEM, SMACSS), CSS Modules, or Shadow DOM
to achieve style isolation. Native @scope provides a built-in solution that works in the
light DOM with no build step or JavaScript required.
Basic @scope Syntax
The @scope rule takes a selector as its scope root. All styles within the @scope
block only apply to elements within that scope root. The same class names can have completely different
styles in different scopes.
In this example, h3 and .button have different colors depending on whether
they're inside .theme-blue or .theme-green. The styles don't leak outside
their respective containers.
Live Example
Scope Boundaries with "to"
The to keyword defines a scope boundary. Styles apply from the scope root
down to (but not including) the boundary element. This is incredibly useful for limiting scope to
specific sections of a component.
Boundary Syntax
/* Scope applies within .card but stops at .card-footer */
@scope (.card) to (.card-footer) {
p {
color: #333;
line-height: 1.6;
}
a {
color: #667eea;
text-decoration: none;
}
}
<div class="card">
<div class="card-header">
<h3>Header</h3>
</div>
<div class="card-body">
<p>Styled by scope</p>
<a href="#">Scoped link</a>
</div>
<div class="card-footer">
<p>NOT styled by scope (outside boundary)</p>
</div>
</div>
Live Example
Component Isolation
One of the most powerful features of @scope is component isolation. You can use simple,
semantic class names like .author, .content, or .actions without
worrying about conflicts with other components using the same names.
These generic class names only affect elements within .comment containers. You could
have .author or .content classes elsewhere in your document with completely
different styles.
Live Example
Proximity-Based Cascade
When multiple scopes could apply to the same element, proximity wins. The scope
that's closest to the element in the DOM tree takes precedence, regardless of specificity or source
order. This is a fundamentally different cascade resolution than traditional CSS.
Proximity Example
/* Outer scope */
@scope (.outer) {
p {
color: red;
}
}
/* Inner scope - closer to nested paragraphs */
@scope (.inner) {
p {
color: blue; /* Wins for paragraphs in .inner */
}
}
<div class="outer">
<p>Red paragraph</p>
<div class="inner">
<p>Blue paragraph (inner scope is closer)</p>
</div>
<p>Red paragraph</p>
</div>
Live Example
Nested and Overlapping Scopes
Scopes can be nested and overlapped to create layered styles. This is perfect for composing
components where base styles are provided by one scope and enhancements are added by another.
Regular widgets get the base dashboard styles, while featured widgets get additional styling
from the more specific scope. Both scopes coexist and complement each other.
Live Example
The :scope Selector
Within a @scope block, you can use the :scope pseudo-class to target
the scope root itself. This is useful when you need to style the container element along with
its descendants.
Using :scope
@scope (.container) {
/* Style the container itself */
:scope {
border: 2px solid #667eea;
padding: 1rem;
}
/* Direct children of container */
:scope > p {
color: #333;
}
/* All paragraphs within container */
p {
line-height: 1.6;
}
}
The :scope selector references the element that matched the scope root selector
(.container in this case). It's similar to & in Sass, but specifically
for @scope contexts.
@scope vs. Other Scoping Methods
There are several approaches to CSS scoping, each with different tradeoffs. Understanding when
to use @scope versus alternatives helps you choose the right tool for your project.
Comparison of Scoping Methods
Method
Pros
Cons
@scope
✅ Native CSS
✅ Proximity-based
✅ Light DOM
✅ No build step
❌ Limited browser support
❌ Newer feature
Shadow DOM
✅ True encapsulation
✅ Widely supported
❌ Requires JavaScript
❌ Complex API
❌ Can't style from outside
BEM/Naming
✅ Universal support
✅ Simple concept
❌ Manual convention
❌ Verbose classes
❌ No true scoping
CSS Modules
✅ True scoping
✅ Build-time safety
❌ Requires build step
❌ Framework-dependent
When to Use Each
@scope: Modern component-based sites, new projects targeting current browsers
Shadow DOM: Web Components, true isolation needed, JavaScript already in use
BEM/Naming: Legacy projects, maximum browser support required
CSS Modules: React/Vue apps, build process already established
Best Practices and Use Cases
Following established patterns helps you get the most value from @scope while
avoiding common pitfalls and ensuring maintainable code.
Best Practices
Keep scopes focused - One scope per component or theme
Use boundaries strategically - Limit scope to relevant sections
Leverage proximity - Let closer scopes override distant ones naturally
Combine with layers - Use @layer for global priority, @scope for isolation
Avoid over-scoping - Not everything needs to be scoped
Document scope roots - Make it clear which elements are scope containers
Common Use Cases
Component libraries: Isolate component styles from application styles
Theming: Apply different themes to different page sections
Third-party widgets: Prevent widget styles from affecting your site (and vice versa)
CMS content areas: Scope styles to user-generated content sections
Shadow DOM alternative: Get scoping benefits while staying in light DOM
Micro-frontends: Isolate styles between different application sections
Design systems: Create portable components that work anywhere
Example: Combining @scope and @layer
/* Use layers for global priority */
@layer reset, components, utilities;
/* Use scope for component isolation */
@layer components {
@scope (.card) {
.title { color: #333; }
.content { line-height: 1.6; }
}
@scope (.modal) {
.title { color: #667eea; }
.content { font-size: 1.1rem; }
}
}