Web development
Cascade layers with a reset: the order that removes !important
@layer decides which rule wins before specificity does, so a reset no longer needs !important. The right order and the three cases where it is not enough.
Key points
- The layer declared first loses: the order of the first declaration matters more than where you write the rule.
- A reset in a lower layer beats the component even with lower specificity: that is the gain.
- Unlayered third-party CSS wins over every layer, so import it with layer(vendor).
Short answer: @layer decides which rule wins before specificity does, so a reset in a lower layer no longer needs !important. Four layers declared at the top of the sheet replace most of the workarounds.
Order is the rule
The layer declared first loses. What matters is not where you write a rule in the file, but the order of the first declaration of each name: one line, @layer reset, tokens, base, components, utilities;, at the top of the sheet fixes everything else.
@layer reset, tokens, base, components, utilities;
@layer reset {
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; }
img { display: block; max-width: 100%; }
:where(h1, h2, h3) { margin: 0; font-size: inherit; font-weight: inherit; }
}
@layer tokens {
:root { --radius: 0.5rem; }
}
@layer base {
body { font: 1rem/1.6 system-ui, sans-serif; }
}
@layer components {
.btn { font-weight: 600; }
}
The reset inside reset loses to .btn inside components even when the reset selector is more specific. That is the gain: specificity stops being the thing people argue about.
Where a layered reset breaks down
| Situation | What happens | Way out |
|---|---|---|
| Unlayered third-party CSS | Its rules sit outside the layers and beat everything | Import it with @import url(...) layer(vendor) |
| Inline style | Inline style beats every layer | Remove the inline style or route it through a variable |
!important inside a layer | The order flips and the lower layer wins | No !important in the reset |
Support covers Chrome 99, Firefox 97 and Safari 15.4. A polyfill makes no sense: on a browser that does not know @layer the rules stay valid, they only lose the guaranteed order.
The limitation
Layers do not settle conflicts inside the same layer: specificity comes back there. A reset written with :where() has zero specificity and stays under control; a reset with long selectors brings the problem back inside the low layer, where it is harder to see.
How to check it
- Open the Styles panel: the rules are grouped by layer and the order is visible at a glance.
- Search for
!importantin your CSS: after a layered reset it should not be needed. - Add a new component and check that you do not have to raise specificity to make it show.
- Rename a layer and confirm that the order line at the top of the sheet is the only place to touch.
Where to continue
In the catalogue: shadcn components, components and blocks. The guides: what Tailwind CSS is and theming shadcn; the comparison: Tailwind against CSS Modules.
Frequently asked questions
Do I have to rewrite all my CSS?
No. Declare the layers at the top and move one sheet at a time: unlayered code keeps working and keeps winning, so the change is gradual.
How do layers coexist with Tailwind?
Tailwind lives in a layer of its own, usually <code>utilities</code>. Put the reset in an earlier layer: a component class then beats the base with no <code>!important</code>.
Does the order change if I import a sheet later?
No, if the layers are already declared. Order comes from the first declaration: a later <code>@import</code> joins the layer it names, not the top.
Do layers cancel :where()?
No, they are different tools. A layer chooses between blocks of rules, <code>:where()</code> zeroes specificity inside a block. They work together.
Why does my reset not beat a component?
Almost always because the component CSS is unlayered: rules outside the layers beat every layer. Import it with <code>layer()</code> or move it inside one.