CSS Scoping
CSS scoping limits a group of rules to a particular part of the page. It is useful when a component needs styles that should not affect similarly named elements elsewhere.
The @scope at-rule is native CSS. Unlike a class prefix such as .card .title, it explicitly declares where a group of rules starts and, optionally, where it stops.
Create a basic scope
Put the scope root in parentheses, then write the rules that belong inside it:
@scope (.product-card) { h2 { color: #0f172a; font-size: 1.25rem; }
a { color: #2563eb; }}Those rules apply only to h2 and a elements inside .product-card. The root itself is not matched unless you use :scope.
@scope (.product-card) { :scope { border: 1px solid #cbd5e1; border-radius: 0.75rem; padding: 1rem; }}Use it with component markup
<article class="product-card"> <h2>Notebook</h2> <p>A compact notebook for everyday notes.</p> <a href="/products/notebook">View product</a></article>
<section class="site-footer"> <a href="/privacy">Privacy</a></section>The link in .product-card receives the scoped color. The footer link does not.
Stop a scope at a boundary
Add to (...) when a nested section should be excluded. The limit element and everything inside it are outside the scope.
@scope (.article) to (.article-actions) { a { color: #0369a1; }}<article class="article"> <p>Read the <a href="/guide">full guide</a>.</p>
<footer class="article-actions"> <a href="/share">Share</a> </footer></article>Only the link in the paragraph is matched by the scoped rule.
Scope states and descendants
Use :scope as the component root when styling state. Use & when you want the same reference in nested CSS.
@scope (.alert) { :scope { background: #eff6ff; border-left: 4px solid #2563eb; }
:scope[data-variant="warning"] { background: #fffbeb; border-left-color: #d97706; }
& .alert__title { font-weight: 700; }}& refers to the scope root in this context, so & .alert__title targets an .alert__title inside .alert.
How scoped rules compete
Scoped rules still follow normal cascade rules: origin, importance, cascade layers, and specificity all matter. When otherwise-equal rules have the same origin and specificity, the rule with the closest scope root wins.
@scope (.card) { .title { color: #334155; }}
@scope (.featured-card) { .title { color: #7c3aed; }}For a .title inside an element with both classes, the .featured-card rule wins when that root is closer to the title.
Prefer clear component boundaries
Scoping works best when the root is a meaningful component wrapper. It is not a replacement for every class name: semantic, reusable names such as .alert__title still make markup easier to understand.
For projects that must support browsers without @scope, keep a prefixed selector fallback or configure the CSS build tool to transform scoped rules:
/* Fallback selector */.product-card h2 { color: #0f172a;}
/* Progressive enhancement */@supports (selector(:scope)) { @scope (.product-card) { h2 { color: #0f172a; } }}Check the browsers your project supports before relying on @scope alone.
Key takeaways
@scope (root)limits a rule group to descendants ofroot.:scopetargets the root element itself.to (limit)excludes a nested boundary and its descendants.- A closer scope root resolves otherwise-equal scoped rules.
- Use meaningful component wrappers and provide a fallback when older-browser support is required.