CSS @scope Reference & Generator
Generate @scope CSS, explore donut scope with scope limits, proximity specificity, and component isolation patterns with interactive examples
The selector for the scope root — e.g. .card, [data-theme]
Scope Rules
Donut Scope
Output
Generated rules (2):
- •
:scope — scope root (.card) - •
h2 — child element
Generated CSS
@scope (.card) {
/* Scoped to descendants of .card */
:scope {
/* Scope root element */
}
h2 {
/* Child element */
}
}You have a .header class in your card component and a .header class in your page layout. They collide. BEM naming (.card__header) solves it but turns your CSS into a verbose naming convention. Shadow DOM isolates styles but requires JavaScript web components. CSS @scope gives you native style scoping in plain CSS: rules inside @scope (.card) only match descendants of .card, and .header stays short, readable, and collision-free.
Why This Reference (Not Just the Chrome Blog Post)
The Chrome blog introduces @scope basics but skips donut scope limits, proximity specificity (how nested @scope rules resolve conflicts), :scope pseudo-class usage, and @scope + @layer interaction. This reference covers the full spec with examples for component isolation, nested theming, and slot-based architectures. Includes a generator for common patterns. Everything runs in your browser; no data is sent anywhere.
What Is the CSS @scope At-Rule?
The CSS @scope at-rule lets you apply styles to a scoped subtree of the DOM — a region defined by a scope root selector and an optional scope limit. Styles inside @scope only match descendants of the scope root, preventing unintended style leakage to the rest of the page.
/* Without @scope — .header class could collide with other .header elements */
.card .header { font-weight: 600; }
/* With @scope — .header is contained to .card descendants only */
@scope (.card) {
.header {
font-weight: 600;
font-size: 1.125rem;
}
}
@scope is now supported in Chrome 118+ and Firefox 128+, and provides a native CSS mechanism for component isolation without shadow DOM or preprocessor conventions like BEM.
Basic @scope Syntax
The @scope at-rule takes a scope root selector in parentheses. All rules declared inside match only descendants of elements matching that selector:
@scope (.card) {
/* h2 only matches h2 elements inside .card */
h2 { font-size: 1.5rem; margin: 0; }
/* p only matches p elements inside .card */
p { color: #4b5563; line-height: 1.6; }
/* .badge only matches inside .card — no global collision */
.badge {
font-size: 0.75rem;
text-transform: uppercase;
}
}
No & nesting selector is required. Rules inside @scope are automatically scoped to the root.
The :scope Pseudo-class
Inside an @scope block, :scope refers to the scope root element itself — the element matching the scope root selector. Use :scope when you need to style the component wrapper directly, not its descendants:
@scope (.card) {
/* :scope = the .card element itself */
:scope {
background: white;
border-radius: 0.5rem;
padding: 1.5rem;
box-shadow: 0 1px 3px rgb(0 0 0 / 0.1);
}
/* Pseudo-classes work on :scope */
:scope:hover {
box-shadow: 0 4px 12px rgb(0 0 0 / 0.15);
}
/* Direct child of scope root */
:scope > .header {
border-bottom: 1px solid #e5e7eb;
padding-bottom: 0.75rem;
}
}
Without :scope, rules match descendants. With :scope, you target the root element itself — equivalent to using the scope root selector directly (.card), but portable.
Donut Scope — Scope with a Limit
Adding a to clause creates a scope limit: the scope ends at (but does not include) elements matching the limit selector. The resulting scoped region has a “donut” shape — it applies between root and limit, not inside the limit:
/* Applies to .card descendants, but NOT inside .card__slot */
@scope (.card) to (.card__slot) {
:scope {
background: white;
border-radius: 0.5rem;
}
.header {
background: #1e293b;
color: white;
padding: 1rem 1.25rem;
}
/* Applies to .card > p but NOT to p inside .card__slot */
p { color: #4b5563; }
}
/* Slot content manages its own styles */
@scope (.card__slot) {
:scope { padding: 1.25rem; }
p { color: #1f2937; font-size: 0.9375rem; }
}
Donut scope is powerful for slot-based component architectures where the component controls its own chrome but leaves user-provided content unstyled.
Scope Limit Syntax
/* Syntax: @scope (scope-root) to (scope-limit) */
@scope (.parent) to (.child-boundary) {
/* Styles the region between .parent and .child-boundary */
}
The scope limit can be any CSS selector. Elements matching the limit selector are excluded from the scope, as are all their descendants.
Proximity Specificity
@scope introduces a new cascade mechanism: proximity. When two @scope rules match the same element, the scope whose root is closest (fewest DOM ancestors away) to the element wins — regardless of specificity or source order.
@scope (.blue-theme) {
a { color: blue; }
}
@scope (.green-theme) {
a { color: green; }
}
/* HTML:
<div class="blue-theme"> ← blue scope, 2 ancestors from <a>
<div class="green-theme"> ← green scope, 1 ancestor from <a>
<a href="#">I am GREEN</a> ← closer scope (green) wins
</div>
</div> */
Without @scope, both rules have equal specificity (0,2,0) and source order decides (blue, declared first, would lose to green only because green is declared last). With @scope, proximity decides — green wins because its root is closer to <a>.
This makes nested theming predictable: the innermost theme always wins without specificity tricks or !important.
Nesting Within @scope
CSS nesting (&) works inside @scope rules exactly as it does at the top level:
@scope (.form) {
:scope {
display: flex;
flex-direction: column;
gap: 1rem;
}
.field {
display: flex;
flex-direction: column;
gap: 0.25rem;
/* CSS nesting inside @scope */
&:focus-within {
outline: 2px solid #3b82f6;
outline-offset: 2px;
border-radius: 0.375rem;
}
}
input {
border: 1px solid #d1d5db;
padding: 0.5rem 0.75rem;
border-radius: 0.375rem;
&:focus { border-color: #3b82f6; outline: none; }
&:invalid:not(:placeholder-shown) { border-color: #ef4444; }
}
}
@media, @supports, and @container can also be nested inside @scope rules for responsive scoped components.
@scope with @layer
Combine @scope with @layer for full cascade control — @layer manages inter-component priority while @scope handles intra-component scoping:
@layer base {
p { color: #374151; line-height: 1.6; }
}
@layer components {
@scope (.card) {
:scope { background: white; border-radius: 0.5rem; }
h2 { font-size: 1.25rem; font-weight: 600; }
p { color: #4b5563; }
}
@scope (.alert) {
:scope { border-left: 4px solid currentColor; padding: 0.75rem 1rem; }
p { font-weight: 500; }
}
}
/* @layer utilities always wins over @layer components */
@layer utilities {
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; }
}
The cascade order with both features is: layer order → specificity → proximity (from @scope) → source order.
Component Isolation Pattern
@scope enables true component isolation without BEM naming conventions. Inner class names stay short and semantic:
/* Before @scope: BEM naming to prevent collisions */
.card__header { font-weight: 600; }
.card__body { color: #4b5563; }
.card__footer { border-top: 1px solid #e5e7eb; }
/* After @scope: short names, no collision risk */
@scope (.card) {
:scope {
background: white;
border-radius: 0.5rem;
padding: 1.5rem;
}
.header { font-weight: 600; font-size: 1.125rem; }
.body { color: #4b5563; line-height: 1.6; }
.footer { border-top: 1px solid #e5e7eb; font-size: 0.875rem; }
}
@scope vs. Shadow DOM
| Feature | @scope | Shadow DOM |
|---|---|---|
| Style encapsulation | Descendants of scope root | Inside shadow root |
| CSS custom properties | Pierce scope | Pierce shadow (unless @property) |
| Specificity | Proximity-based | Separate style sheet |
| JS required | No | Yes (for JS-defined components) |
| Progressive enhancement | Yes | Partial |
| Browser support | Chrome 118+, Firefox 128+ | All modern browsers |
@scope is best for CSS-only component isolation in regular DOM. Shadow DOM is best for true encapsulation in web components.
Browser Support
| Browser | @scope | Donut Scope | :scope in @scope |
|---|---|---|---|
| Chrome | 118+ | 118+ | 118+ |
| Firefox | 128+ | 128+ | 128+ |
| Safari | Not yet | Not yet | Not yet |
| Edge | 118+ | 118+ | 118+ |
| Opera | 104+ | 104+ | 104+ |
Global coverage: approximately 72–75% as of early 2026. Safari support is in active development. For Safari fallback, write flat selectors first and use @scope as a progressive enhancement.
Detecting Support
@supports at-rule(@scope) {
/* @scope is supported */
@scope (.card) {
:scope { background: white; }
}
}
Frequently Asked Questions
What is the difference between @scope and a descendant selector?
A descendant selector like .card h2 { } also limits h2 to .card descendants. The key differences: @scope rules use proximity for conflict resolution (not specificity/source order), @scope supports the :scope pseudo-class for styling the root itself, and @scope supports the to clause (donut scope) to exclude inner regions. @scope is also more composable — rules inside can use short selectors without risking global collisions.
Does @scope change specificity?
@scope does not add specificity points. The specificity of a scoped rule is determined by the rule’s selector alone — the scope root and limit do not contribute specificity. Instead, @scope adds a proximity tiebreaker: when specificity is equal, the closer scope wins.
Can I use @scope with CSS custom properties?
Yes. Custom properties declared inside @scope inherit normally through the DOM. Declaring --color-text: blue on :scope inside @scope (.card) means all elements inside .card that use var(--color-text) will get blue — unless an inner scope re-declares the variable.
Is @scope the same as CSS Modules or CSS-in-JS scoping?
Conceptually similar, but native and build-tool-free. CSS Modules and CSS-in-JS scope styles by transforming class names at build time. @scope achieves scoping at the browser level using DOM proximity, with no class name mangling required. The tradeoff: @scope requires newer browsers (Chrome 118+, Firefox 128+).
Can @scope be used inline in a <style> tag?
Yes. @scope works in <style> elements in HTML. When used in a <style> element without arguments (@scope { }) inside a component, the scope root is automatically the element containing the <style> tag (applicable in declarative shadow DOM scenarios).
Does nesting work inside @scope?
Yes. CSS nesting (&) works inside @scope rules with identical behavior to top-level nesting. @media, @supports, and @container can also be nested inside @scope rules.
Related Tools
- CSS Nesting Reference & Generator — Learn the & nesting selector, pseudo-class nesting, and media queries inside rules
- CSS @layer Reference & Generator — Configure cascade layers and visualize priority order
- CSS Container Queries Reference — Generate @container CSS for component-level responsive design
- CSS Selector Specificity Calculator — Calculate (a,b,c) specificity scores for CSS selectors