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
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.
Completed: 0 / 2 exercises
Knowledge Check
Question 1 of 3