Glossary

CSS cascade layers

CSS cascade layers are named groups of style rules, created with the @layer at-rule, whose declared order decides conflicts before selector specificity does. They are specified in CSS Cascading and Inheritance Level 5, a W3C Candidate Recommendation Snapshot dated 13 January 2022. Browsers shipped them in 2022, and web-features lists Chrome 99, Firefox 97 and Safari 15.4 as the first versions.

How it works

A layer is created in one of three ways:

  • Block: @layer base { ... } puts the rules inside into the layer named base.
  • Statement: @layer reset, base, components; creates layers with no rules, in the order listed.
  • Import: @import url(theme.css) layer(theme); puts a whole file into a layer. The spec's keyword form layer or layer() makes an anonymous one.

Layers are ranked by the order in which each name first appears, anywhere in the stylesheet. A later @layer line that repeats a name does not reorder it. Rules that sit in no layer are treated as one implicit final layer, so they outrank every named layer for normal declarations. For !important the order flips, and the earliest layer wins.

Names can be nested with dots, as in @layer framework.theme, or by putting one block inside another. A parent layer's own rules form an implicit sublayer after its named sublayers, which means they win over them. A block with no name creates an anonymous layer that cannot be added to later.

@layer reset, base, components;
@layer components { .btn { color: green; } }
@layer reset { #main .btn { color: red; } }
@layer base { button.btn.big { color: blue; } }

Markup: <div id="main"><button class="btn big">. Computed color in headless Chromium 141:

#a  rgb(0, 128, 0)

The ID rule in reset has far higher specificity than .btn, but components was listed last, so green wins.

Does the order of the @layer statement or the blocks decide priority?

Whichever mentions a name first decides it. In a test, a sheet with a components block first, then a base block, then the statement @layer components, base; ended with base on top, and the button color was rgb(0, 0, 255). The late statement did not move components. To control order, put the statement at the top of the first stylesheet, before any @import.

Why do unlayered styles beat layered ones?

Unlayered rules sit in an implicit last layer, so they win normal declarations against any named layer regardless of specificity. In a test, .btn { color: green; } outside any layer beat #main .btn { color: red; } inside a layer, and the result was rgb(0, 128, 0). This lets your own CSS beat a layered framework without raising specificity.

Common pitfalls

  • Nested layer surprise: in @layer framework { @layer theme {...} .t {...} }, the direct rule beats the theme sublayer. In a test, both .t and .u colors resolved to rgb(0, 0, 255), the direct rule, even though the theme rule was written first.
  • Expecting a late statement to reorder: a repeated name keeps its first position. Declare the order once, up front.
  • Importing after an @layer rule: the spec warns that an @layer block cannot be interleaved with @import, and an @layer statement between imports makes later imports be ignored.
  • Marking resets important: important declarations reverse the layer order, so a reset layer declared first with important declarations beats everything that follows. Keep resets normal.
  • Assuming anonymous layers are reusable: each @layer { } without a name creates a new layer. Name the layer if you need to add rules later.
  • Adding specificity to fight layers: a higher-specificity rule in an earlier layer still loses. Move the rule to the right layer instead.

Related terms

  • CSS cascade — the full sorting order where layers are one step
  • CSS specificity — the weight that layers override across layer boundaries
  • CSS :where() — the other tool for keeping base rules easy to override
  • CSS nesting — works inside layers and can nest @layer blocks
  • CSS @scope — a separate proximity step that comes after layers and specificity

See also