Reference guide · html-css-js · Published 2026-08-16 · 3 min read
Scoping styles with the CSS @scope rule
Use CSS @scope to limit where rules apply, avoid selector leakage between components, and simplify cascade reasoning.
- ·How it bounds rules
- ·Syntax and limits
- ·Use it alongside layers
CSS @scope bounds a set of rules to a subtree, so a component's styles apply inside its container and stay out of unrelated parts of the document. It became stable and broadly supported across all major engines late in 2025, joining Chrome, Edge, Safari and Firefox around the same time, which makes it a practical way to contain component styles without inventing naming conventions.
How it bounds rules
The idea is that instead of writing .widget .title and crossing your fingers that no other .title gets hit, you declare a boundary. Rules inside the block only apply to elements within the scope root (the element you named) and optionally its scope limit (the deepest point the scoping should reach). This does two useful things at once: it prevents a component's internal selectors from leaking out, and it prevents rules from another component, or from the wider page, from leaking in unless the cascade wins.
@scope (.card) {
.title { color: rebeccapurple; }
a { text-decoration: none; }
}
Here .title and a are matched only inside elements that have the card class. Outside the card, those selectors are untouched. The scope root is like the :scope pseudo at the root of the group, and you can also write a limit:
@scope (.card) to (.card__footer) {
p { margin: 0 0 1em; }
}
The second form stops the match at .card__footer: paragraphs inside the footer do not get the scoped rule. This subtree-style scoping removes the need to repeat a long ancestor chain in each selector.
Syntax and limits
The important mental model is that @scope is about selector reach, not about specificity or order in the way layers are. A scoped rule still competes in the normal cascade against any rule that matches the same element, and no special specificity boost is applied. That matches how native CSS behaves: the boundary reduces the set of elements a rule can target, and the usual cascade rules decide which of two matching selectors wins. See the specificity guide for how those conflicts settle.
Two practical limits are worth knowing. Scoping does not protect against author styles that target the same element from outside; if a global rule sets .title { color: red } and it wins the cascade, the scoped rule loses, which is the intended, spec-compliant result. And because support is recent, treat narrow old browsers as out of scope: for a broader reach you can still use a naming or class convention, but @scope removes most of the load that conventions carry.
Use it alongside layers
Combine @scope with cascade layers and nesting to get both boundaries and order. Layers control which stylesheet, and which set of component styles, wins when rules conflict; scopes control how far a rule reaches. Scaffold a component like this:
@layer components, base;
@scope (.card) {
.title {
color: oklch(40% 0.2 260);
}
}
The layer gives the component's rules a slot in the overall order, and the scope keeps them inside the card. Read the nesting guide for how scoped blocks nest with each other, and the cascade-layers guide to place a scoped component in the global order. Measured together they let you ship component styles that are both local and predictable, which is the property @scope was designed to deliver.