PureDevTools

CSS @scope Reference & Generator

Generate @scope CSS, explore donut scope with scope limits, proximity specificity, and component isolation patterns with interactive examples

All processing happens in your browser. No data is sent to any server.

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@scopeShadow DOM
Style encapsulationDescendants of scope rootInside shadow root
CSS custom propertiesPierce scopePierce shadow (unless @property)
SpecificityProximity-basedSeparate style sheet
JS requiredNoYes (for JS-defined components)
Progressive enhancementYesPartial
Browser supportChrome 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@scopeDonut Scope:scope in @scope
Chrome118+118+118+
Firefox128+128+128+
SafariNot yetNot yetNot yet
Edge118+118+118+
Opera104+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

More CSS Tools