🚀AdvancedAdvanced CSS Architecture

CSS @scope

Limit selector reach to a specific subtree of the DOM and create proximity-based styling without hacks using the native CSS @scope rule.

Overview

@scope limits where a group of CSS rules applies. Instead of writing long descendant selectors (.card .title, .card .body, ...) or relying on preprocessor nesting, @scope natively bounds styles to a specific element subtree. ```css /* Without @scope */ .card .title { color: #0f172a; } .card .body { color: #6b7280; } /* With @scope */ @scope (.card) { .title { color: #0f172a; } .body { color: #6b7280; } } ``` **Scope with a lower boundary (donut scope):** ```css /* Applies to .card descendants, but stops at .inner-card */ @scope (.card) to (.inner-card) { .title { color: #0f172a; } } ``` **Proximity wins over specificity in scope:** When the same element matches multiple @scope blocks at different nesting depths, the rule from the closest ancestor scope wins, not the most specific selector. **The :scope pseudo-class** refers to the scope root itself: ```css @scope (.card) { :scope { border-radius: 12px; } /* styles the .card itself */ .title { font-weight: 700; } /* styles .card .title */ } ```

Key Points

  • •@scope (.root) limits rules to descendants of .root
  • •@scope (.root) to (.limit) creates a donut: applies between root and limit but not inside limit
  • •:scope inside @scope refers to the scope root element itself
  • •Proximity beats specificity for scoped rules: the closer ancestor scope wins
  • •@scope in a <style> element without arguments scopes to the element's parent
  • •Multiple scope blocks can exist in one stylesheet
  • •@scope helps avoid deep selector chains and keeps component CSS self-contained
  • •Supported in Chrome 118+, Safari 17.4+, Firefox 128+

Examples

Basic @scope for component isolation

/* Scope all card rules to inside .card */
@scope (.card) {
  :scope {
    background: white;
    border: 1px solid #e5e7eb;
    border-radius: 12px;
    overflow: hidden;
    font-family: sans-serif;
  }

  .title {
    font-size: 1rem;
    font-weight: 700;
    color: #0f172a;
    margin: 0 0 6px;
  }

  .body {
    font-size: 0.875rem;
    color: #6b7280;
    line-height: 1.5;
  }

  .footer {
    border-top: 1px solid #f1f5f9;
    padding: 10px 16px;
    font-size: 0.8rem;
    color: #94a3b8;
  }
}

.title inside @scope (.card) is equivalent to .card .title but without repeating .card on every rule.

Donut scope to avoid style leaking into nested components

/* Apply .light-theme styles between .theme-light and any nested .theme-dark */
@scope (.theme-light) to (.theme-dark) {
  :scope {
    background: #f8fafc;
    color: #0f172a;
  }

  .btn {
    background: #6366f1;
    color: white;
  }

  .card {
    background: white;
    border: 1px solid #e5e7eb;
  }
}

/* Nested dark theme: not affected by the scope above */
@scope (.theme-dark) {
  :scope {
    background: #0f172a;
    color: #f1f5f9;
  }

  .btn {
    background: #818cf8;
  }
}

The "to" boundary stops .theme-light styles from leaking into any nested .theme-dark component.

Proximity-based theming

/* Two scope blocks for the same class */
@scope (.surface-light) {
  .text { color: #0f172a; }
  .label { color: #6b7280; }
}

@scope (.surface-dark) {
  .text { color: #f1f5f9; }
  .label { color: #94a3b8; }
}

/* An element inside both (nested) picks the closer one */
/* <div class="surface-dark">
     <div class="surface-light">
       <p class="text">Picks surface-light rules (closer ancestor)</p>
     </div>
   </div> */

Proximity-based resolution replaces the need for increasing specificity just to override an outer theme.

@scope in HTML <style> element

<!-- Scope to the host component without a class name -->
<article>
  <style>
    @scope {
      /* Scoped to the parent <article> element */
      :scope { font-family: Georgia, serif; }
      h2 { font-size: 1.5rem; color: #0f172a; }
      p  { line-height: 1.7; color: #374151; }
      a  { color: #6366f1; }
    }
  </style>
  <h2>Article Title</h2>
  <p>Content <a href="#">linked here</a></p>
</article>

A <style> tag with @scope (no root argument) automatically scopes to its parent element, enabling co-located component styles in HTML without classes.

Common Mistakes

  • Forgetting that @scope still requires the element to be a descendant; it does not create encapsulation like Shadow DOM
  • Using @scope (.a) to (.b) and expecting .b itself to be excluded (the boundary is exclusive)
  • Overusing @scope when CSS nesting already handles the same use case with less syntax
  • Expecting @scope to prevent external styles from affecting elements (it only scopes what your @scope block applies)

Note

Use @scope for theming and component isolation in large CSS files where you want to avoid deep selectors. Use CSS nesting (@scope sibling) when the context is already clear and you just want less repetition.

Exercises

Write CSS to style the HTML scaffold. The preview updates live — when it looks right, mark as done.

Loading editor…

Completed: 0 / 2 exercises

Knowledge Check

Question 1 of 3

What does @scope (.parent) to (.child) limit?