CSS Cascade Layers and Specificity: A Calmer Mental Model
Stop fighting !important. Learn how the CSS cascade, specificity, and the new @layer rule combine to give you predictable, maintainable styles.
What you'll learn
- ✓How the CSS cascade decides which rule wins
- ✓How specificity is actually counted
- ✓What @layer does and why it changes everything
- ✓Where !important and inline styles really sit
- ✓How to organize a real stylesheet with layers
Prerequisites
- •Basic CSS familiarity
What and Why
When two CSS rules try to style the same property on the same element, the browser has to pick one. That selection process is the cascade. Most “why is my CSS not applying” questions are really cascade questions.
For decades, specificity was the main lever, and projects ended up in escalating !important wars. Cascade layers (@layer, shipped in all major browsers since 2022) give you a clean way to declare ordering up front, without specificity gymnastics.
Mental Model
When the browser resolves a property, it walks several tiers in order. Within each tier, specificity and source order break ties.
1. transitions
2. !important user-agent
3. !important user
4. !important author
5. animations
6. normal author <-- @layer ordering applies here
7. normal user
8. normal user-agent
9. revert / unset / initial Within the “normal author” tier, layers are evaluated in declaration order: rules in earlier layers lose to rules in later layers, regardless of selector specificity. Unlayered styles win over any layered style by default.
Specificity inside a layer is still counted as four numbers (inline, IDs, classes/attrs/pseudo-classes, elements/pseudo-elements). Higher wins; ties go to whatever appears later.
Hands-on Example
Without layers, a third-party CSS file can be hard to override because its selectors are too specific. With layers, you declare an order:
@layer reset, base, components, utilities;
@layer reset {
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; }
}
@layer base {
body { font-family: system-ui, sans-serif; color: #111; }
a { color: #06f; }
}
@layer components {
.btn {
padding: 0.5rem 1rem;
border-radius: 0.5rem;
background: #06f;
color: #fff;
}
}
@layer utilities {
.text-center { text-align: center; }
.mt-2 { margin-top: 0.5rem; }
}
Now any rule in utilities beats any rule in components, even if the component selector is more specific. You no longer need !important or hyper-specific selectors to make a utility “stick”.
Import a third-party stylesheet into its own layer to keep it tame:
@import url("vendor.css") layer(vendor);
@layer vendor, base, components, utilities;
By placing vendor first in the declared order, your own layers automatically win.
Specificity inside a layer:
@layer components {
.btn { color: white; } /* (0,1,0) */
.card .btn { color: gray; } /* (0,2,0) wins inside layer */
}
But across layers, declaration order of layers dominates:
@layer a, b;
@layer b {
.btn { color: red; } /* (0,1,0) */
}
@layer a {
#main .btn { color: blue; } /* (1,1,0) but in earlier layer */
}
Inside @layer b, color: red wins. Specificity does not save the blue rule because layer a was declared first and therefore loses to layer b.
Unlayered styles win against any layered style:
@layer components { .btn { color: red; } }
.btn { color: green; } /* not in any layer - wins */
This is convenient for quick overrides but dangerous if abused; treat unlayered styles as a small escape hatch.
Common Pitfalls
Declaring layers after using them. The first occurrence of @layer name, name2; sets the order. If you write a @layer components {...} block first and only later say @layer utilities, components;, the order may not be what you expected. Put your layer order declaration at the very top.
Mixing layered and unlayered styles without intent. Unlayered wins, so a stray rule outside any layer can quietly override the layer system you just built.
Reaching for !important to escape a layer. !important flips a rule into a higher tier where layer order is reversed (earlier layers win). It works, but it is a confusing tool. Prefer fixing the layer order or moving the rule.
Assuming inline styles always win. They beat author styles in the normal tier but lose to !important author styles. Inline style="..." is also annoying to override; avoid where possible.
Confusing the specificity of :is() and :where(). :is() takes the specificity of its most specific argument; :where() is always zero. :where() is great for low-specificity defaults.
Practical Tips
Start every project with one short prelude:
@layer reset, base, components, utilities, overrides;
This guarantees predictable ordering before any other rule arrives.
Wrap component libraries in their own layer so app code can override them without specificity wars.
Use :where() to give defaults that anything else can override:
:where(a) { color: var(--link); }
Now even a single class selector wins, no !important required.
Audit !important periodically. Each one is a small admission that the cascade got away from you. With layers, most can be removed.
Tools like the browser dev tools’ “Computed” tab now show layer names next to rules. Use them when chasing why a property is not applying.
Wrap-up
The cascade picks a winner by walking tiers, then layers, then specificity, then source order. Specificity used to be the only knob; @layer adds a much better one. Declare your layer order, group reset, base, components, and utilities, and you can stop typing !important and start trusting your stylesheet again.
Related articles
- HTML & CSS CSS Container Queries Explained with Real Examples
Container queries let components style themselves based on their parent's size, not the viewport. Learn the syntax, the units, and when to use them.
- HTML & CSS CSS Grid vs Flexbox: When to Use Each (with Examples)
Stop flipping a coin between Grid and Flexbox. Learn the mental model that picks the right layout tool every time, with practical CSS examples.
- HTML & CSS CSS :has() Pseudo-Class Tutorial
Learn how the CSS :has() pseudo-class enables true parent selectors, conditional styling, and sibling-aware rules. Includes a mental model, examples, pitfalls, and best practices for production use.
- HTML & CSS CSS Modern Color Functions
A practical tour of modern CSS color: oklch, color-mix, relative color syntax, and wide-gamut color spaces. Learn how to pick palettes that stay perceptually even and accessible across themes.