Every large CSS codebase eventually accumulates !important declarations. Bootstrap utilities use them. Tailwind's ! prefix exists for them. Frameworks add them defensively, then your overrides need their own !important, then someone else's reset wins anyway. Cascade layers stop the war.
The basic idea
@layer reset, base, components, utilities;
@layer reset {
* { margin: 0; padding: 0; }
}
@layer base {
body { font: 16px/1.5 system-ui, sans-serif; color: #1f2937; }
}
@layer components {
.button { padding: .75rem 1rem; border-radius: 6px; }
}
@layer utilities {
.mt-4 { margin-top: 1rem; }
}
The first line declares an order. Later layers always beat earlier layers, regardless of selector specificity. So an unstyled .mt-4 (one class) wins over .button.button-primary[data-state="active"] (huge specificity) — because it's in a layer declared later.
Anything outside layers wins over anything inside layers.
Why this changes everything
Before layers, the cascade was:
- !important
- inline styles
- id selectors
- class/attribute/pseudo-class selectors
- element selectors
You "won" by being more specific. The arms race: .btn → .btn.btn-primary → body .btn.btn-primary → !important → end of conversation.
With layers:
- !important (still nuclear)
- inline styles
- Unlayered styles
- Layers, in declared order (last wins)
Inside a layer, normal specificity still applies — but only against other rules in the same layer. Cross-layer comparisons obey the order. Predictable.
Real example: third-party + your overrides
/* In a single stylesheet */
@layer vendor, components, utilities, overrides;
@layer vendor {
/* Bootstrap dump goes here */
@import url("bootstrap.css");
}
@layer components {
.card { background: #fff; padding: 1.5rem; }
}
@layer utilities {
.p-0 { padding: 0; }
}
@layer overrides {
.card { background: #f3f4f6; }
}
Your .card override in the overrides layer beats Bootstrap's, without needing !important or higher specificity. Even Bootstrap's .card.card-primary[data-bs-toggle] loses, because the layer order said so.
Three patterns I keep using
1. Reset that doesn't fight everything else
@layer reset, *;
@layer reset {
* { margin: 0; padding: 0; box-sizing: border-box; }
}
The * in the layer-order means "every other layer goes after reset." Reset rules sit at the bottom, every named or unnamed layer wins.
2. Tailwind-style utilities that always win
@layer base, components, utilities;
@layer utilities {
.text-center { text-align: center; }
.hidden { display: none; }
}
Now .hidden works without the Tailwind ! prefix or !important. It's just last in the order.
3. Theming
@layer theme.light, theme.dark;
@layer theme.light {
:root { --bg: white; --text: #111; }
}
@layer theme.dark {
[data-theme="dark"] { --bg: #111; --text: #fafafa; }
}
Dot-notation creates sublayers. Useful for organization; works the same as flat layers for ordering.
Caveats
- Anonymous unlayered styles win. If a stylesheet is partially layered, the unlayered parts beat all layers. Not always what you want — declare a default layer like
@layer base { ... }to avoid surprises. @importwith layer:@import url("x.css") layer(vendor);puts the whole imported sheet into one layer. Useful for wrapping legacy CSS.- Nested layers (
@layer components.card { ... }) are cool but rarely needed. Keep it flat. !importantSTILL nukes everything. Cascade layers don't replace!important— they replace the need for it.
Browser support (2026-05)
Caniuse: all major browsers since Q1 2022. Chrome 99, Safari 15.4, Firefox 97. Three years of universal support.
What this kills
- CSS-in-JS libraries that exist only to scope styles. If you wanted styled-components for "components win over global,"
@layer componentsdoes it natively. - The
!importantepidemic. Most uses of!importantare workarounds for "this should win." Layers express that intent declaratively. - Shadow DOM as a "specificity hack". Layers don't replace Shadow DOM's encapsulation, but for simple "make my override win," they're cheaper.
I removed every !important from a 2k-line stylesheet last quarter using nothing but cascade layers and shorter selectors. The cascade became readable again.
Hands-on CSS practice on Code Crispies — see the css-fundamentals module for selectors and specificity exercises with live preview.
