Cascade Layers & @scope
As stylesheets grow, specificity battles become the main source of pain. Cascade layers (@layer) let you decide which groups of styles win, regardless of specificity. @scope limits styles to a part of the page. Together they bring order to large projects.
The problem
A highly specific rule — perhaps from a third-party theme — beats your simpler utility class, and the usual "fixes" are more specificity or !important.
Defining layers
The first line declares the layer order. A rule in a later layer always wins over one in an earlier layer — specificity only matters within a layer. So the simple .button-primary in utilities beats the id selector in base.
Layer order rules
- Order is fixed by the first time each layer name appears — that's why declaring them all up front is good practice.
- Unlayered styles beat all layered styles. Code you haven't put in a layer has the highest priority.
- For
!importantdeclarations the order reverses: important styles in earlier layers win.
Putting third-party CSS in a layer
@layer vendor, components, utilities;
@import url("https://cdn.example.com/framework.css") layer(vendor);
@layer components {
.card { border-radius: 12px; }
}
Importing a framework into a low-priority layer means your own styles override it without specificity hacks — one of the most practical uses of layers.
Nested layers
@layer components {
@layer buttons {
.btn { padding: 8px 16px; }
}
@layer cards {
.card { padding: 16px; }
}
}
@layer components.buttons {
.btn-small { padding: 4px 10px; }
}
Layers can contain sub-layers, referenced with dot notation.
A typical layer architecture
| Layer | Contents |
|---|---|
reset | Normalising browser defaults, box-sizing |
vendor | Third-party CSS |
base | Element styles: typography, links, forms |
layout | Page structure |
components | Cards, buttons, navigation |
utilities | Single-purpose helper classes that should always win |
@scope
@scope (.card) limits the rules inside to elements within .card. Inside, :scope refers to the scope root itself.
Scoping with a lower boundary
The to (…) clause creates a "donut scope": styles apply inside .article but not inside nested .comments. This is something ordinary descendant selectors can't express.
Scope proximity
When two scoped rules have the same specificity, the one whose scope root is closest to the element wins. That makes nested themes work naturally — a light card inside a dark section takes the light styles. @scope is supported in Chromium browsers and Safari, and arriving in Firefox.
But paragraphs inside the comments section don't — the scope stops at .comments.