CSS Cascade Layers (@layer)
CSS Cascade Layers, introduced by the @layer at-rule, provide a powerful mechanism for organizing CSS and controlling the cascade more predictably. They solve a long-standing problem in large-scale CSS projects: how to manage specificity conflicts between different sources of styles (e.g., framework, base, components, and utilities).
The Core Concept: Styles within a higher-priority layer will always win over styles in a lower-priority layer, regardless of the selector's specificity within those layers.
1. The Cascade Layer Order
The final result of a style depends on a fixed order of precedence. Cascade Layers slot into the standard cascade but allow you to define a sub-order for your own styles.
The Key Layer Precedence Rule:
- Earlier layers have lower priority (less important).
- Later layers have higher priority (more important).
This means a simple selector in a late-defined layer can override a highly specific selector in an early-defined layer.
Order of Author Styles (Lowest to Highest Priority)
- Styles within the first defined
@layer(e.g.,reset). - Styles within later defined
@layers (e.g.,utilities). - Unlayered Styles (CSS not wrapped in any
@layerblock). - Author
!importantdeclarations.
2. Defining and Ordering Layers
You must define the order of your layers using a single @layer statement, typically placed at the very top of your CSS file.
2.1. Defining the Order
/* 1. Define the layer order (Lowest to Highest Precedence) */
@layer reset, framework, base, components, utilities;
In this setup: reset has the lowest priority, and utilities has the highest priority among the layers.
2.2. The Specificity vs. Layer Rule
This is the most critical concept to understand:
Layer Order Trumps Specificity: The specificity of a selector only matters within its own layer. Once two properties conflict across two different layers, the layer order determines the winner, ignoring specificity.
Consider two rules:
.box(specificity: 0,1,0) in thebaselayer.#box-id(specificity: 1,0,0) in theutilitieslayer.
If utilities is defined after base, the simple .box utility will override the highly specific #box-id selector, because the utilities layer has higher precedence. This is how layers flip the cascade.
2.3. Creating Layer Blocks
You then wrap your styles in named @layer blocks. You can define layers across multiple files or multiple blocks in the same file; the browser will stitch them together based on the initial ordering statement.
/* -- 1. Reset Layer (Lowest Priority) -- */
@layer reset {
/* This universal selector style is easily overridden by any other layer. */
* {
box-sizing: border-box;
}
}
/* -- 2. Components Layer (Mid Priority) -- */
@layer components {
/* Specific component style */
.card-title {
color: var(--theme-color);
}
}
3. Unlayered Styles (The Override)
Styles that are not included in any @layer block are referred to as unlayered styles.
Unlayered styles always have a higher precedence than all layered styles, regardless of their position in the file or the layer order.
Use unlayered styles sparingly, typically only for final, non-negotiable overrides or small third-party snippets where integrating them into a layer is impractical. Relying heavily on unlayered styles defeats the purpose of managing the cascade with layers. The goal should be to make 99% of your styles layered.
/* This style is NOT wrapped in @layer. Specificity: 0,1,0 */
.final-override {
/* This will win over ALL layered styles, even if the selector is simpler. */
background-color: green;
}
/* This is a layered style that loses to the unlayered style above. Specificity: 0,1,0 */
@layer utilities {
.final-override {
background-color: red;
}
}
4. Layer Organization and Naming
A common layer strategy is to move from the least specific, broadest styles to the most specific, overriding styles.
| Layer Name | Typical Content | Priority | Role |
|---|---|---|---|
reset | CSS resets (e.g., normalize.css), global box-sizing. | Lowest | Provides a clean slate. |
framework | Third-party libraries (Tailwind base, Bootstrap). | Low | Imports external defaults. |
base | Element selectors (h1, p, a), custom fonts, root variables. | Mid-Low | Defines project defaults. |
components | Reusable, complex class selectors (e.g., .modal, .card). | Mid-High | Defines component structure. |
utilities | Single-purpose, overriding classes (e.g., .u-hidden, .u-text-red). | Highest | Ensures utility classes always win. |
Nested and Imported Layers
Layers can be nested, and styles can be imported directly into a layer, making framework integration seamless.
Nesting layers helps organize large component libraries (e.g., separating forms from navigation).
/* Initial layer declaration must include the top level: */
@layer framework, components;
/* Importing an external file into the 'framework' layer */
@import url('bootstrap.css') layer(framework);
/* Defining a nested layer within components */
@layer components.forms {
/* Styles for form components */
.input-field { border-color: gray; }
}
Styles in components.forms have the same precedence as components styles.
Interactive CSS Layers Demo
This example demonstrates the precedence: The utility layer is defined later than the component layer, so the utility's style always wins. The unlayered style wins over both.