ESC

Type to search the knowledge base.

Cascade Layers @layer

Use @layer to order reset, base, components, and utilities so specificity wars lose — layer priority, unlayered CSS, and !important.

intermediate3 min read
  • css
  • cascade-layers
  • architecture

Cascade layers (@layer) let you declare priority buckets for whole groups of rules. A utility in a later layer can beat a component selector even when the component’s selector is “more specific.” That is the point: stop climbing specificity to win overrides.

Docs: MDN cascade layers, @layer.

Declare order first

@layer reset, base, components, utilities;

Later names in that list win over earlier ones for normal (non-!important) declarations when both set the same property.

@layer reset {
  * {
    margin: 0;
  }
}

@layer components {
  .button {
    background: var(--brand);
    color: white;
    padding: 0.5rem 1rem;
  }
}

@layer utilities {
  .bg-transparent {
    background: transparent;
  }
}
<button type="button" class="button bg-transparent">Ghost</button>

.bg-transparent wins on background even though both are single classes — layer order, not selector weight, decided it.

Unlayered CSS is a boss fight

Any styles outside @layer act like a final layer higher than layered normal styles (for normal declarations). That surprises people who append a “quick fix” at the bottom of a file:

@layer components {
  .card {
    padding: 1rem;
  }
}

/* Unlayered — beats layered components */
.card {
  padding: 2rem;
}

Team convention: either put everything in layers, or treat unlayered as emergency-only.

Nested and anonymous layers

@layer framework {
  @layer components {
    .btn { }
  }
}
/* Referenced later as framework.components */
@layer {
  /* anonymous — order is first-appearance */
}

Prefer named layers and one explicit order list at the top of the entry CSS.

Interaction with specificity and importance

For competing declarations, the cascade roughly considers:

  1. Origin & importance
  2. Layer (for that origin/importance)
  3. Specificity
  4. Order

Within the same layer, specificity and source order still apply.

@layer components {
  .card.title { color: blue; }   /* higher specificity */
  .title { color: red; }
}
/* .card.title wins inside the layer */

!important reverses layer order among important declarations (important in earlier layers can win). That is hard to reason about — avoid !important except rare utilities or third-party overrides.

Practical architecture

@layer reset, tokens, base, layout, components, utilities;

@layer tokens {
  :root {
    --space-2: 0.5rem;
    --brand: #0b6bcb;
  }
}

@layer base {
  body {
    font-family: system-ui, sans-serif;
    line-height: 1.5;
  }
}

@layer utilities {
  .sr-only { /* … */ }
  .mt-2 { margin-top: var(--space-2); }
}

Frameworks (Bootstrap layers, Tailwind’s layered build, design-system packages) increasingly ship this way so host apps can slot overrides into a known layer.

Importing into layers

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

Useful when third-party CSS shouldn’t stomp your utilities — wrap the import in a low layer.

Interview out-loud

“@layer groups rules into ordered buckets. Later layers override earlier ones regardless of specificity for normal declarations. Unlayered CSS beats layered CSS, so I keep a single ordered list like reset → base → components → utilities and avoid stray unlayered fixes. !important inverts layer priority and I treat it as a last resort.”

Footguns

  1. Forgetting the initial order list and relying on first-appearance order only.
  2. Leaving debug CSS unlayered.
  3. Using layers and ultra-specific selectors — still a mess inside a layer.
  4. Assuming layers replace cascade knowledge — they don’t; they reorder a step.
  5. Shipping critical CSS without the layer order preamble.

Further reading

Related guides