Overview
@layer lets you declare named buckets and an explicit precedence between them. Selector specificity is resolved inside a layer; the layer order resolves between layers. The result is a cascade you can read off the top of the file instead of debugging with DevTools. The umbrella for CSS architecture is css.
Declare layer order once, at the top
A single @layer declaration sets the order. Later layers win, regardless of specificity inside them.
@layer reset, base, components, utilities, overrides;
@layer reset {
/* normalize, box-sizing */
}
@layer base {
:root {
--color-ink: oklch(0.2 0 0);
}
body {
font-family: system-ui;
}
}
@layer components {
.card {
padding: var(--space-3);
border-radius: var(--radius-card);
}
}
@layer utilities {
.text-center {
text-align: center;
}
}
@layer overrides {
.force-block {
display: block;
}
}Anything written outside any layer wins over everything inside layers. Use that lane for true emergency overrides only, and document why.
Use the five-layer stack: reset, base, components, utilities, overrides
The names are a convention, but this set covers most apps.
reset: normalize, box-sizing, focus-visible defaults.base: token definitions, element defaults (body,h1,a), css-custom-properties on:root.components: reusable component classes,[data-variant]modifiers.utilities: single-purpose utilities (Tailwind’s output goes here; see tailwind).overrides: page-level fixes, scoped temporary patches.
A new layer is sometimes justified (a vendor lane for third-party CSS, a themes lane for skinning). Add it to the declaration line; do not let order drift.
Quarantine third-party CSS in its own layer
Most cascade pain comes from a vendor stylesheet whose specificity outranks yours. Wrap it in a layer that sits below your code.
@layer vendor, reset, base, components, utilities;
@import url("/static/vendor/calendar.css") layer(vendor);Now your .calendar-day { color: var(--color-ink); } in components wins over the vendor’s .calendar .day { color: #333; }, without !important and without selector escalation.
Retire !important from app code
!important skips the layer order. A single !important in components outranks every rule in utilities. That defeats the point.
- App rules: never use
!important. If you need to win, move the rule to a later layer. - Utility classes inside
@layer utilities: do not stamp!important. The layer order already wins. - Library or print stylesheets that must defeat host CSS: justified, isolated to one file.
If !important is the only fix, the underlying problem is a layer-ordering bug. Find it and fix the order instead.
Layer-aware specificity inside a layer
Inside a single layer, normal specificity applies: a class beats an element, an ID beats a class. Across layers, specificity is irrelevant; the layer order wins. Two rules to remember.
- Specificity is intra-layer.
- Order is inter-layer.
A #sidebar a rule in base loses to a .link rule in utilities. The id wins inside base; the layer order wins across.
Debug layers in DevTools
Chrome, Firefox, and Safari all show layer assignments in the Styles panel.
- Each rule lists its layer name next to the selector.
- The cascade column shows which rules are overridden and why (layer, specificity, source order).
- Filter by
@layerto see only the rules that won for an element.
If a rule shows up grey with another layer’s rule winning, the fix is to move the rule to a later layer, not to escalate the selector.
Order Tailwind and shadcn carefully
Tailwind v4 emits its utilities into @layer utilities and its base reset into @layer base. shadcn components ship as raw CSS or as classes that expect to live in @layer components. Set the order explicitly so utilities can override components.
@import "tailwindcss";
@layer reset, base, components, utilities;Without an explicit declaration, the order falls to import order, and a stray third-party stylesheet imported late can outrank your utilities.
Common pitfalls
- A rule outside any layer wins over a rule inside one. Forgetting to wrap an
@importis the usual cause. Addlayer(name)to the import. - Unnamed
@layer { }blocks. Anonymous layers join the declaration order in source order. Use named layers so the order line is the source of truth. - Animations and
@keyframesinside a layer. The animation name resolves across layers as normal cascading; the@keyframesrule itself is not layered. Define keyframes once, outside layers or inbase. - Mixing legacy
!importantwith layers. A legacy!importantrule inverts the layer order for that property. Audit and remove. - Authoring tokens in
components. Tokens belong inbaseso every later layer can read them; see css-custom-properties.