Cascade Layers
Date: 2026-09-27
@layergives you an explicit override order that outranks specificity. Declare the order once at the top, and a one-class selector inutilitiesbeats an ID selector incomponents— so nobody has to escalate selectors to win.
Cascade layers are named buckets of CSS with an author-declared precedence, checked by the cascade before specificity. Within a layer the usual specificity and source-order rules still apply; between layers, only the layer order counts — The Cascade and Specificity.
The rule, in one block
/* 1. declare the order once. later = stronger */
@layer reset, base, components, utilities;
@layer components {
#checkout .button { background: navy; } /* (1, 1, 0) */
}
@layer utilities {
.bg-red { background: red; } /* (0, 1, 0) — WINS */
}
.button { background: green; } /* unlayered — beats BOTH */Three things to notice:
- The order line decides everything. A layer’s position is fixed by its first mention, so the one-line declaration up front is what makes the order deliberate rather than an accident of file order
- Specificity never crosses a layer boundary. The ID in
componentsloses to one class inutilities - Unlayered styles sit above every layer. The easy one to miss, and it’s a deliberate choice — adopt layers gradually, and anything you haven’t moved in yet still wins
Where things go
weakest @layer reset normalise, box-sizing
@layer base element defaults: body, a, h1–h6
@layer vendor third-party CSS, imported into a layer
@layer components .card, .modal
@layer utilities .visually-hidden, .mt-4
strongest (unlayered) overrides, legacy CSS not yet migrated
Third-party CSS is the best argument for layers. A library with heavy selectors, imported into a low layer, becomes overridable by any single class of yours:
@import url("datepicker.css") layer(vendor);Before layers the options were matching its specificity or reaching for !important.
!important runs backwards
For !important declarations the layer order reverses: the earliest layer’s !important wins. It’s deliberate — a reset can mark something as non-negotiable and no later layer can undo it — but it’s the opposite of what everyone expects, and it means !important in utilities is weaker than !important in reset.
Tradeoffs and failure modes
- Unlayered legacy CSS wins by default. Layer your new component CSS while old global CSS stays unlayered, and the old CSS beats it everywhere. Migration usually means wrapping the legacy CSS in a low layer first, not last
- Nested layers (
@layer components.forms) are ordered inside their parent. Useful; also a second ordering to keep in your head - Frameworks may emit layers of their own — a utility framework’s generated CSS might arrive already layered, and its order has to fit yours [CHECK: whether the utility framework in use emits native
@layerblocks and in what order] - It doesn’t fix a wrong model. Layers remove the need to fight specificity; they don’t stop someone writing
#page .cardinside the component layer
Supported in all current major browsers [CHECK: confirm against current Baseline data if an older-browser audience matters]. Where it isn’t, a layered stylesheet doesn’t partly work — the whole @layer block is ignored, so ship a fallback or don’t layer critical styles — Browser Compatibility.
Where it connects
- CSS Architecture — layers are the native version of what ITCSS approximated through naming discipline
- Utility-First vs Semantic CSS — utilities in a top layer is how the two approaches coexist without
!important