Home CSS

CSS @layer — explicit cascade layers

@layer creates named, ordered slots in the cascade so authors can predict which rule wins without specificity wars or !important escalation. The single largest improvement to cascade authoring since custom properties.

What @layer does

The CSS cascade has long resolved conflicts in two stages: origin (user agent < user < author < animations) and then within the author origin by importance (!important flips order), specificity, and source order.

Cascade Layers add a third stage within the author origin: a named, ordered set of layers between origin and importance. Author rules in a later-declared layer beat author rules in an earlier layer, regardless of selector specificity.

@layer reset, base, components, utilities;

@layer reset {
  * { margin: 0; padding: 0; }
}
@layer base {
  body { font-family: var(--font-body); line-height: 1.6; }
}
@layer components {
  .btn { padding: 0.5rem 1rem; border-radius: 4px; }
}
@layer utilities {
  .pad-0 { padding: 0; }
}

Even though .btn and .pad-0 have equal specificity, the utilities layer wins because it was declared after components. Source order within a layer still matters in the usual way.

Why layers matter

Before @layer, authors used specificity wars: stack classes, qualify with :where(), or escalate to !important. Each escalation made future overrides harder. Layers let the cascade do its job by intent rather than accident.

A practical sequencing pattern from Miriam Suzanne’s 2022 talk:

@layer reset, base, theme, layout, components, overrides;
  • reset — UA normalisation.
  • base — element defaults (typography, spacing scale).
  • theme — design tokens, palettes.
  • layout — page-level grid, containers.
  • components — buttons, cards, forms.
  • overrides — utilities, page-specific tweaks.

A rule in overrides always wins over a rule in components, without bumping specificity. Multiple components can ship rules in components without fighting one another.

Importing into layers

@import accepts a layer() function:

@import url("normalize.css") layer(reset);
@import url("framework.css") layer(components);

This puts a third-party stylesheet into a controlled layer. The common gotcha is that some libraries declare their own @layer ... { ... } blocks; the layer() import wraps the entire file, which may produce nested layers like reset.bootstrap.utilities.

Anonymous layers

@layer { ... } (no name) creates an anonymous layer that is unique to its source position. Two anonymous layers in the same file are separate; their relative order follows source order. Anonymous layers cannot be re-opened by name later.

Reverting and unlayered rules

Rules outside any @layer block are unlayered and beat all layered rules at the same origin. This is intentional: it lets you escape the layer system with a quick rule:

@layer base, components, utilities;
@layer base { h1 { font-size: 2rem; } }
@layer components { .heading { font-size: 1.5rem; } }
h1.heading { font-size: 1.75rem; }   /* unlayered — wins */

Avoid relying on this in mature codebases — the unlayered rule is invisible in the layer report tools.

DevTools support

Each engine’s DevTools surfaces layers in the Styles pane:

  • Chromium DevTools (since 113, 2023) shows layer names next to matching rules.
  • Firefox DevTools (since 117, 2023) shows layers in the inspector cascade view.
  • Safari Web Inspector (since 16.4, 2023) shows layers in the Styles sidebar.

Pass-rate on the Cascade L5 layer test suite is about 98% across all three engines as of 2024.

Common pitfalls

  • Forgetting to declare order at the top. Layer order is fixed at first encounter; subsequent declarations cannot reorder. Always declare @layer a, b, c; first.
  • Mixing layered and unlayered rules. Unlayered rules beat layered ones — surprising when audit tooling assumes everything is in a layer.
  • !important and layers. Importance ordering is reversed for layered rules: an !important rule in an earlier layer wins over an !important rule in a later layer. Combined with origin importance, this lets user-agent !important still win — but author authors are surprised.
  • Specificity inside a layer still matters. Layers do not collapse specificity; they are an outer key. A high-specificity rule in components still beats a low-specificity rule in components.

Cross-engine support

@layer reached interop in 2022 across Chromium 99 (March 2022), Firefox 97 (February 2022), and Safari 15.4 (March 2022). caniuse data reports about 95% global support.

Further reading