skip to content

How does the browser establish the order of CSS cascade layers, and what happens when you re-open a named @layer block later in the same stylesheet?

level: middleimportance: should knowfreq 34%

answer

  1. first mention fixes the slot
  2. re-opening appends, never promotes
  3. one statement at the top of the entry file
  4. unknown name joins at the end
  5. anonymous blocks are separate and unnameable

basics

~20 s

Layer order is fixed by where each layer name first appears, not by where its rules sit. Re-opening a named layer appends declarations to the existing layer and never moves it, which is why teams declare the full order once at the top with a @layer statement.

solid answer

~50 s

A layer takes its position from its **first** appearance, and after that the position is frozen. That first appearance can be a rules block, or — much better — a statement like `@layer reset, base, components, utilities;` that names every layer up front and contains nothing. Once the order exists, writing `@layer components { ... }` again anywhere later simply appends those rules to the existing `components` layer; it does not move `components` to the end. That is what makes layers robust against file order: you can `@import` partials in any sequence, or ship CSS from several bundles, and the override order still comes from that one statement. A name that was never listed gets appended to the order at the point it first shows up. Anonymous `@layer { ... }` blocks each create a separate layer at their position and can never be reopened, since there is no name to refer to.

code

css · 5 lines
css
@layer reset, vendor, base, components, utilities;

@layer components { .card { padding: 1rem; } }
@layer base { body { margin: 0; } }
@layer components { .card__title { font-weight: 700; } }

go deeper

for a junior

Know the shape of the ordering statement, @layer reset, base, components;, and that it goes at the top of the stylesheet before the rules that use those names.

for a middle

Explain that first appearance fixes the position and later blocks only append. Predict the winner in a snippet where the blocks appear in a different order from the statement.

for a senior

Show why this makes bundling and import order irrelevant, and flag the real hazards in review: mistyped layer names silently creating a strongest-last layer, and anonymous layers that nobody can extend.

for a principal

Own the ordering statement as an architectural contract — who may add a layer, where library layers slot in, and how you keep a mistyped or ad-hoc layer name from becoming a permanent override channel.

## First appearance fixes the position The cascade sorts layers by the order in which their names are **first encountered** while parsing the stylesheet. Everything after that first encounter is appending, not reordering. ```css @layer base { a { color: teal; } } @layer utilities { .u-red { color: red; } } @layer base { a { text-decoration: none; } } /* appends to base, base stays first */ ``` Here the order is `base`, then `utilities`, and the third block does not promote `base` above `utilities`. If it did, layer order would depend on which file happened to be concatenated last, and the whole mechanism would be as fragile as the source order it was meant to replace. ## The statement form is the intended pattern Because first appearance wins, you can declare the entire architecture in one line before any rules exist: ```css @layer reset, vendor, base, layout, components, utilities; @import url("vendor.css") layer(vendor); @import url("components.css") layer(components); @import url("base.css") layer(base); ``` The imports are deliberately out of order to make the point: the override order is `reset < vendor < base < layout < components < utilities` no matter how the files are loaded or bundled. This single statement is also the best documentation the stylesheet has — a reader learns the whole override policy from one line. ## Undeclared names are appended If a block uses a name that no statement listed, that name joins the order at that moment, after every layer already known: ```css @layer base, components; @layer debug { .outline * { outline: 1px solid red; } } /* debug lands after components */ ``` That is convenient but also the main way a codebase drifts: a typo like `@layer compoents { ... }` silently creates a brand-new layer at the end instead of adding to `components`, and because that new layer is *last*, the rules start winning more than intended rather than failing loudly. ## Nested layers Layers nest, and a nested layer's full name is dotted: ```css @layer framework { @layer base, theme; } @layer framework.theme { .btn { background: navy; } } ``` The inner order (`framework.base` before `framework.theme`) is resolved inside the parent, and the whole `framework` group keeps its own position relative to sibling top-level layers. Nesting matters when you ship a library: you expose one outer layer name, and consumers can slot your entire sub-order into their own architecture with `@layer reset, framework, app;` without knowing your internal names. A sub-layer can never escape its parent — `framework.theme` still loses to any top-level layer ordered after `framework`. You can also reach a nested layer by nesting the blocks instead of dotting them; `@layer framework { @layer theme { ... } }` is equivalent to the dotted form. ## Anonymous layers `@layer { ... }` with no name creates a layer at that position that has no identity: ```css @layer { .a { color: red; } } @layer { .b { color: blue; } } ``` These are two *different* layers, not one, and neither can be referenced, reopened, or targeted by an ordering statement. They are useful for a self-contained block you want demoted below unlayered styles, and a liability anywhere you might later need to insert something between them. ## Why this matters in review The practical rules that fall out of this: - Put one `@layer a, b, c;` statement at the top of the entry stylesheet and treat it as the source of truth. - Never rely on the order of `@import`s or bundler output to establish layer order. - Treat an unlisted layer name in a diff as a smell — it is either a typo or an architecture change that should have been made in the ordering statement. - Prefer named layers over anonymous ones in shared code, so future rules can be appended to them. ## The compact answer Order comes from first mention; later mentions only add rules. Declare the order once, deliberately, and every other file becomes order-independent.

  • If a stylesheet uses @layer with a name that no ordering statement listed, where does that layer end up?
    It is appended to the layer order at the point where the name is first seen, so it lands after every layer already known at that moment. That is why a mistyped layer name is dangerous rather than harmless: the typo creates a new last layer whose rules win more often than the author intended.
  • How do nested layers such as framework.theme fit into the top-level order?
    The sub-layers are ordered among themselves inside their parent, and the parent occupies a single slot in the top-level order. So `framework.theme` can beat `framework.base`, but it can never beat a top-level layer declared after `framework`. That containment is what lets a library expose one outer name that consumers slot into their own ordering statement.
  • Why prefer named layers over anonymous ones in shared code?
    An anonymous `@layer { ... }` block cannot be referenced, so nobody can append to it, order it explicitly, or use `revert-layer` against it by name — and two anonymous blocks are two separate layers rather than one. Names cost nothing and keep the architecture editable later.

saying these in an interview costs you the question

  • Thinks a later @layer block moves the layer to the end
  • Says layer order follows the order of @import statements
  • Assumes re-opening a layer creates a second, stronger layer
  • Believes a nested layer can outrank a later top-level layer
  • Treats two anonymous @layer blocks as the same layer

context