CSS: @scope at-rule
ai answer
Can you explain CSS at rules at scope?
Sure. CSS @scope is a newer at-rule that lets you limit which styles apply to which parts of your HTML, without relying on complex selectors or high specificity.
It works like this: you define a scope root, and then only the selectors inside the @scope block will match elements that are descendants of that root.
A quick example: @scope (.card) { button { color: blue; } } — that means only buttons inside any element with class "card" get the blue color. Super clean way to avoid style leakage.
It’s still rolling out across browsers, but it’s really useful for component-based styling. Want me to show you a more practical example or compare it to other scoping methods like Shadow DOM?
How is it different from using descendant selector?
It's different in three key ways.
First, @scope has zero specificity impact — your styles inside it don't get boosted like they would with a .card button descendant selector.
Second, it respects proximity — if the same element could match two scopes, the one closest in the DOM wins, not the one with higher specificity.
Third, it's much cleaner to read and maintain — especially with multiple nested components.
Basically, descendant selectors fight with specificity wars. @scope gives you clean boundaries without that mess.
scope tutorial
ai generated
CSS @scope at-rule lets you limit the reach of your selectors to specific DOM subtrees. It helps you write cleaner, more maintainable CSS for components without overly specific selectors, BEM-style naming, or CSS-in-JS workarounds.
Why Use @scope?
Traditional CSS is globally scoped, so selectors can leak and require high specificity or tight DOM coupling. @scope solves this by defining upper (inclusive) and lower (exclusive) boundaries for styles.
Benefits:
- Lower specificity inside the scope (easier to override).
- Better encapsulation for components.
- Proximity-based cascade resolution (closer scope roots win).
- No need for unique class names or preprocessors for scoping.
Browser Support: Baseline (newly available as of late 2025). Supported in Chrome/Edge 118+, Safari 17.4+, Firefox 146+.
Basic Syntax
@scope (<scope-root>) [to (<scope-limit>)]? { /* scoped style rules */ }
<scope-root>— Upper boundary (inclusive). Styles apply to descendants of matching elements.<scope-limit>(optional) — Lower boundary (exclusive). Styles stop before these elements (the "donut hole").- Multiple roots/limits use comma-separated lists.
Inline <style> variant (no prelude needed):
<parent> <style> @scope { /* styles scoped to the parent of <style> */ } </style> </parent>
You can also do @scope to (<limit>) { ... } for inline styles.
Examples
1. Basic Scope (Upper Bound Only)
@scope (.card) { img { border: 4px solid green; } }
This styles only <img> elements inside any .card.
2. Donut Scope (Upper + Lower Bound)
@scope (.card) to (.card__content) { img { border: 4px solid green; } }
Styles apply between .card and .card__content (images in the header but not the content area).
3. Multiple Limits (Multiple "Holes")
@scope (.container) to (.post, footer) { p { color: blue; } }
<p> elements inside .container are blue, except those inside .post or footer.
4. Using :scope
The :scope pseudo-class refers to the scoping root inside the block.
@scope (.feature) { :scope { background: rebeccapurple; color: antiquewhite; } img { /* descendants only */ } }
You can use it in limits for precise relationships:
@scope (.article-body) to (:scope > figure) { img { /* only direct-child figures are excluded */ } }
5. The & Nesting Selector vs :scope
Inside @scope:
- Bare selectors and
&behave like:where(:scope)→ zero specificity added. - Explicit
:scopeadds pseudo-class specificity (0-1-0).
@scope (.card) { img { /* specificity 0-0-1 */ } :scope img { /* specificity 0-1-1 */ } & img { /* specificity depends on root; higher if ID used */ } }
Scope Proximity in the Cascade
@scope adds scoping proximity to the cascade (after specificity, before source order). When specificities match, the style with the fewest hops to its scope root wins.
Classic problem without @scope:
.light-theme p { color: black; } .dark-theme p { color: white; } /* wins by source order */
Nested light theme inside dark would incorrectly get white text.
With @scope:
@scope (.light-theme) { p { color: black; } } @scope (.dark-theme) { p { color: white; } }
The closer scope root wins. Perfect for nested themes/components.
Advanced Tips
- Inclusive/Exclusive Bounds: Use child combinators for control, e.g.
@scope (.root > *) to (.limit > *). - Nesting
@scope: Allowed and narrows further. - Inheritance:
@scopelimits selectors, not styles. Inherited properties (likecolor) can leak beyond the lower bound. - Global At-Rules:
@keyframes,@font-face,@layerinside@scoperemain global. - Invalid Selections: You cannot escape the subtree (e.g.
:scope + pis invalid).
Full Demo Structure
<div class="card"> <style> @scope { :scope { border: 2px solid blue; } h2 { color: navy; } } </style> <h2>Scoped Title</h2> <img src="..." alt=""> </div>
Best Practices & When to Use
- Use for component libraries or large pages with reusable sections.
- Combine with CSS Layers (
@layer) for even better cascade control. - Fallback for older browsers: Duplicate rules outside
@scope(they'll be overridden where supported).
@scope is a powerful native tool that reduces reliance on naming conventions and JavaScript. Experiment with it in component-based UIs—it shines where Shadow DOM scoping would be overkill.
For the full spec details, check MDN @scope. Happy scoping!