Overview

@layer lets you declare named buckets and an explicit precedence between them. Selector specificity is resolved inside a layer; the layer order resolves between layers. The result is a cascade you can read off the top of the file instead of debugging with DevTools. The umbrella for CSS architecture is css.

Declare layer order once, at the top

A single @layer declaration sets the order. Later layers win, regardless of specificity inside them.

@layer reset, base, components, utilities, overrides;
 
@layer reset {
  /* normalize, box-sizing */
}
@layer base {
  :root {
    --color-ink: oklch(0.2 0 0);
  }
  body {
    font-family: system-ui;
  }
}
@layer components {
  .card {
    padding: var(--space-3);
    border-radius: var(--radius-card);
  }
}
@layer utilities {
  .text-center {
    text-align: center;
  }
}
@layer overrides {
  .force-block {
    display: block;
  }
}

Anything written outside any layer wins over everything inside layers. Use that lane for true emergency overrides only, and document why.

Use the five-layer stack: reset, base, components, utilities, overrides

The names are a convention, but this set covers most apps.

  • reset: normalize, box-sizing, focus-visible defaults.
  • base: token definitions, element defaults (body, h1, a), css-custom-properties on :root.
  • components: reusable component classes, [data-variant] modifiers.
  • utilities: single-purpose utilities (Tailwind’s output goes here; see tailwind).
  • overrides: page-level fixes, scoped temporary patches.

A new layer is sometimes justified (a vendor lane for third-party CSS, a themes lane for skinning). Add it to the declaration line; do not let order drift.

Quarantine third-party CSS in its own layer

Most cascade pain comes from a vendor stylesheet whose specificity outranks yours. Wrap it in a layer that sits below your code.

@layer vendor, reset, base, components, utilities;
 
@import url("/static/vendor/calendar.css") layer(vendor);

Now your .calendar-day { color: var(--color-ink); } in components wins over the vendor’s .calendar .day { color: #333; }, without !important and without selector escalation.

Retire !important from app code

!important skips the layer order. A single !important in components outranks every rule in utilities. That defeats the point.

  • App rules: never use !important. If you need to win, move the rule to a later layer.
  • Utility classes inside @layer utilities: do not stamp !important. The layer order already wins.
  • Library or print stylesheets that must defeat host CSS: justified, isolated to one file.

If !important is the only fix, the underlying problem is a layer-ordering bug. Find it and fix the order instead.

Layer-aware specificity inside a layer

Inside a single layer, normal specificity applies: a class beats an element, an ID beats a class. Across layers, specificity is irrelevant; the layer order wins. Two rules to remember.

  • Specificity is intra-layer.
  • Order is inter-layer.

A #sidebar a rule in base loses to a .link rule in utilities. The id wins inside base; the layer order wins across.

Debug layers in DevTools

Chrome, Firefox, and Safari all show layer assignments in the Styles panel.

  • Each rule lists its layer name next to the selector.
  • The cascade column shows which rules are overridden and why (layer, specificity, source order).
  • Filter by @layer to see only the rules that won for an element.

If a rule shows up grey with another layer’s rule winning, the fix is to move the rule to a later layer, not to escalate the selector.

Order Tailwind and shadcn carefully

Tailwind v4 emits its utilities into @layer utilities and its base reset into @layer base. shadcn components ship as raw CSS or as classes that expect to live in @layer components. Set the order explicitly so utilities can override components.

@import "tailwindcss";
 
@layer reset, base, components, utilities;

Without an explicit declaration, the order falls to import order, and a stray third-party stylesheet imported late can outrank your utilities.

Common pitfalls

  • A rule outside any layer wins over a rule inside one. Forgetting to wrap an @import is the usual cause. Add layer(name) to the import.
  • Unnamed @layer { } blocks. Anonymous layers join the declaration order in source order. Use named layers so the order line is the source of truth.
  • Animations and @keyframes inside a layer. The animation name resolves across layers as normal cascading; the @keyframes rule itself is not layered. Define keyframes once, outside layers or in base.
  • Mixing legacy !important with layers. A legacy !important rule inverts the layer order for that property. Audit and remove.
  • Authoring tokens in components. Tokens belong in base so every later layer can read them; see css-custom-properties.