Cascade Layers for Reset and Design Tokens
Specificity battles are the tax of a growing stylesheet: a reset uses broad selectors, a component overrides them, a utility tries to override the component, and soon every rule is fighting for priority with !important and selector stacking. The @layer rule fixes this at the root by letting authoring order, not specificity, decide who wins. This guide, part of the modern CSS reset strategies collection within Mastering Container Queries & Responsive Layouts, shows how to order a reset, design tokens, and components into predictable layers, then drop container queries on top without disturbing the stack.
Approach rationale: priority by intent, not by selector
Without layers, the cascade ranks normal declarations by specificity, so a reset written as * is weak but a reset written as ul li a quietly outranks a component's .btn. Authors then escalate specificity to win, and the stylesheet becomes a ladder no one can climb back down. Cascade layers invert the model: you name an ordered set of layers once, and every rule's priority is decided first by which layer it sits in. A reset in an early layer can use whatever selectors it likes and will still lose to a later component layer, regardless of specificity.
The win is architectural clarity. Reset goes first because it should always be overridable. Design tokens — the custom properties that define colour, spacing, and type scales — sit next so components can consume them. Components come after tokens. Utilities, if you use them, come last so a single class can override a component on demand. There is no JavaScript and no preprocessor involved; this is native cascade behaviour and the right baseline for any design system that also leans on container queries for component responsiveness.
Complete working implementation
This self-contained file declares the layer order once, then fills each layer. Resize the wrapper to confirm the container query, which lives in the components layer, overrides cleanly without specificity tricks.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<style>
/* Declare the order ONCE, up front. Layers listed here rank in this order
no matter where their rules are physically written later. */
@layer reset, tokens, components, utilities;
/* RESET layer: broad selectors, intentionally low priority. */
@layer reset {
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; }
/* Even this specific selector loses to the components layer below. */
ul.menu li a { color: black; text-decoration: underline; }
}
/* TOKENS layer: design decisions exposed as custom properties.
Custom properties are not subject to layer ordering themselves
(they cascade as values), but housing them here documents intent. */
@layer tokens {
:root {
--color-accent: #7aa2ff;
--space-1: 0.5rem;
--space-2: 1rem;
--radius: 8px;
--font-ui: system-ui, sans-serif;
}
}
/* COMPONENTS layer: consumes tokens, overrides reset by layer order. */
@layer components {
.card-shell { container-type: inline-size; container-name: card; }
.menu {
display: flex;
gap: var(--space-2);
list-style: none;
margin: 0;
padding: var(--space-1);
font-family: var(--font-ui);
}
/* A single-class selector beats the reset's ul.menu li a purely
because components is a later layer than reset. No !important. */
.menu a { color: var(--color-accent); text-decoration: none; }
/* Container query lives in the SAME layer as the component it adjusts,
so its overrides rank with the component, not against it. */
@container card (max-width: 360px) {
.menu { flex-direction: column; gap: var(--space-1); }
}
}
/* UTILITIES layer: last, so one class can override a component. */
@layer utilities {
.text-muted { color: #6b7280; }
}
</style>
</head>
<body>
<div class="card-shell">
<ul class="menu">
<li><a href="#">Home</a></li>
<li><a href="#">Docs</a></li>
<li><a href="#" class="text-muted">Archive</a></li>
</ul>
</div>
</body>
</html>
The diagram below shows how the engine ranks these layers for normal declarations.
Key technique callout: declaring layer order up front
The single most important line is @layer reset, tokens, components, utilities; at the very top. This statement names the layers and fixes their order before any of them contain rules. From that point the physical position of a @layer reset { ... } block in the file no longer matters — you could write the reset last and it would still rank lowest. This decoupling is what lets large teams append component styles anywhere without re-reasoning about specificity: priority is a property of the named layer, decided once. Forget this declaration and layers instead rank in first-seen order, which is fragile and bug-prone.
Variation: nested layers and an imported reset
Larger systems sublayer for finer control, and external resets can be imported straight into a layer so third-party CSS never escapes your ordering. Tokens defined here also pair naturally with cross-area animation work, such as fluid spacing tokens driving transition durations.
/* Pull a third-party reset directly into the reset layer. */
@import url("modern-normalize.css") layer(reset);
/* Sublayers: components.base ranks below components.overrides. */
@layer components {
@layer base, overrides;
@layer base { .btn { padding: 0.5rem 1rem; } }
@layer overrides { .btn--ghost { background: transparent; } }
}
Sublayers nest the same rule recursively: within components, the overrides sublayer beats base, while the whole components layer still beats reset. Anything imported with layer(reset) is permanently subordinate to your components, no matter how aggressive the vendor selectors are.
Browser support note
Cascade layers are Baseline and shipped in Chrome and Edge 99, Safari 15.4, and Firefox 97, so @layer is safe across all current engines in 2026. The @import ... layer() syntax landed alongside the rule in the same versions. If you must support older browsers, note that an unlayered fallback stylesheet will simply win everywhere (unlayered beats layered), so guard with @supports only if a layered rule must not leak — for example @supports at-rule detection via @layer is not yet a thing, so feature-detect by shipping the layered sheet to modern engines and a flattened sheet otherwise.
FAQ
What problem do cascade layers actually solve? They let you declare cascade priority by authoring order rather than by selector specificity. A reset placed in an early layer can use simple selectors and still be overridden by component styles in a later layer, even when the reset's selectors are more specific.
Where do unlayered styles fall in the cascade?
Unlayered styles win over all layered styles of the same origin and importance. Anything you leave outside @layer effectively becomes the highest-priority normal layer, which is why most architectures layer everything or keep only deliberate overrides unlayered.
Do container queries need their own layer?
Not necessarily. A @container rule carries the specificity of the selectors inside it, so it belongs in whichever layer those component styles live. Keeping component and container-query rules in the same layer keeps responsive overrides predictable.
How do !important interact with cascade layers?
Important declarations reverse layer order: among important styles, earlier layers win over later ones. This is deliberate, letting a reset or utility layer assert non-negotiable rules that components cannot accidentally override.
Related
- Modern CSS Reset Strategies — the parent guide on building a spec-compliant baseline.
- How to Use Container Queries in Production — scoping component queries that live inside these layers.
- Building Responsive Cards with Container Queries — a component layer in practice.
- CSS Custom Properties Architecture — cross-area guide on structuring the design tokens housed in the tokens layer.
Related articles
More pages in the same section.