CSS @scope

What You'll Learn

  • 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.

Creating Scoped Styles

@scope (.theme-blue) { h3 { color: #667eea; } .button { background: #667eea; color: white; } } @scope (.theme-green) { h3 { color: #28a745; } .button { background: #28a745; color: white; } }

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.

Scoped Component Example

@scope (.comment) { .author { font-weight: 600; color: #667eea; } .timestamp { color: #999; font-size: 0.875rem; } .content { margin: 0.5rem 0; line-height: 1.6; } .actions { display: flex; gap: 1rem; } .action-button { background: none; border: none; color: #667eea; cursor: pointer; } }

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.

Composing Scopes

/* Base dashboard scope */ @scope (.dashboard) { .widget { background: white; border: 1px solid #ddd; padding: 1rem; } .widget-title { font-weight: 600; color: #333; } } /* Enhanced scope for featured widgets */ @scope (.widget.featured) { .widget-title { color: #667eea; font-size: 1.25rem; border-bottom: 2px solid #667eea; } }

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; } } }

Key Takeaways