CSS Cascade Layers (@layer)
In CSS, styling conflicts are traditionally resolved using Specificity (IDs beat classes, classes beat elements) and Source Order (later rules override earlier rules).
As projects grow, this often leads to "specificity wars"—where developers write increasingly complex selectors (like #sidebar .card div.btn) just to override styling from third-party frameworks.
CSS Cascade Layers (@layer) introduce a new tier to the CSS cascade, allowing developers to define explicit priority layers that override specificity entirely.
1. The Core Problem: Specificity Wars
Imagine you load a third-party UI library that defines a button:
/* Third-party library styles */
.widget-container button.action-btn {
background-color: blue;
padding: 10px;
}Now, in your custom theme utilities, you want to override that button background color to be orange:
/* Your custom theme stylesheet */
.btn-orange {
background-color: orange;
}Even if your stylesheet loads after the library, the library button remains blue.
Why? Because .widget-container button.action-btn has a specificity score of 0, 2, 1 (two classes, one element), whereas your .btn-orange only has 0, 1, 0 (one class). To fix this, you are forced to write a more specific selector or resort to using !important.
2. The Solution: Cascade Layers (@layer)
Cascade Layers let you organize styles into distinct buckets (layers). The priority of these layers is defined explicitly at the top of your stylesheet.
The Golden Rule:
[!IMPORTANT] Styles declared in a higher-priority layer will ALWAYS override styles from a lower-priority layer, regardless of selector specificity.
Syntax:
First, declare the order of your layers (from lowest priority to highest priority):
/* Define layer order at the very top of your stylesheet */
@layer framework, components, utilities;Next, wrap your CSS rules inside @layer blocks:
/* Low-priority framework styles */
@layer framework {
.widget-container button.action-btn {
background-color: blue;
padding: 10px;
}
}
/* High-priority utility styles */
@layer utilities {
.btn-orange {
background-color: orange;
}
}What is the result?
Even though the framework selector .widget-container button.action-btn has much higher specificity, the button will render orange. Because the utilities layer is declared after the framework layer in our order statement, it has higher priority, rendering specificity arguments irrelevant.
3. How Unlayered Styles Behave
A critical rule to remember when using Cascade Layers is how unlayered styles (normal CSS rules not wrapped in a @layer) behave.
- Unlayered styles always have the highest priority.
- They will override any styles defined inside any layer, regardless of specificity.
This design ensures that:
- Legacy styles or basic sheets work out-of-the-box.
- Cascade layers are opt-in.
@layer components {
.card { background: white; } /* Lower Priority */
}
.card { background: grey; } /* Unlayered - Higher Priority, Wins! */4. Layer Ordering Best Practices
To avoid confusion, always define the order of your layers at the very beginning of your primary stylesheet:
/* Establish layer hierarchy immediately */
@layer reset, base, framework, components, custom-utilities;
/* Imports can also be assigned to layers */
@import url("bootstrap.css") layer(framework);By doing this, you guarantee that framework rules can never accidentally bleed out and lock down custom styles, protecting your codebase from specificity inflation.
Key Takeaways
- Cascade Layers (
@layer) let you group CSS rules into explicit priority structures. - Layer order wins over selector specificity. High-priority layer classes override low-priority layer IDs.
- Define your layer stack hierarchy at the very top of your CSS files (e.g.,
@layer base, theme, utilities;). - Unlayered CSS rules act as the ultimate override, ranking higher than all layered rules.