CSS @layer in Practice —
Escaping the Specificity War for Good

You add a UI library, and suddenly your carefully written styles are losing battles they shouldn't. You add !important. Then another. Then one more. @layer is the way out — it lets you declare who wins before the fight even starts.

How the !important Spiral Starts

Here's a scenario that every front-end developer has lived through. You drop in a third-party UI library. Its button styles override yours because its selectors are more specific — .ui-lib .btn.primary beats .btn every time. So you add !important to win. Then the library's hover style still wins somehow. So you add another. And then you're in a war.

The root cause is that CSS's cascade resolves conflicts using two implicit axes: specificity and source order. Both are invisible rules baked into the language. Throw a third-party library into the mix, and those rules become a moving target you can't control.

CSS Cascade Layers — the @layer rule — landed in Chrome and Firefox in 2022 as a direct answer to this problem. The idea is simple but powerful: let developers declare style priority explicitly, independent of specificity.

📌 Key Point
@layer separates who wins from how specific the selector is. You define the priority order once, at the top. Then specificity only matters within each layer.

The Basics — Declare Priority, Then Write Styles

The core rule: later layers win. You define named layers, assign styles to them, and the layer order determines who overrides whom — regardless of selector specificity.

Basic @layer syntax
/* Step 1: Declare the priority order upfront */
@layer base, components, utilities;

/* Step 2: Assign styles to each layer */
@layer base {
  a { color: blue; }
}

@layer components {
  .btn { color: white; background: #059669; }
}

@layer utilities {
  .text-red { color: red; } /* Wins — last layer declared */
}

That first line — @layer base, components, utilities; — is doing a lot of work. It sets the priority order for the entire stylesheet. Everything that follows can be written in any order; the declared sequence is what matters.

▶ Live Demo — Layer Priority in Action
Click to toggle .active
Active state (utilities wins)

Click the left box to add .active. The demoEN-utilities layer is last in the declared order, so its red color beats demoEN-base and demoEN-components — even though all three target the same element.

Three Real-World Patterns

① Trap third-party libraries in the lowest layer

This is the use case that converts people. Drop the library into a low-priority layer, and your own styles always win — no specificity battles, no !important.

Containing a third-party library
/* Priority order: right wins over left */
@layer vendor, base, components, overrides;

/* Trap the library in the lowest layer */
@layer vendor {
  @import url('some-ui-library.css');
}

/* Your styles always win over vendor, no matter how specific the library is */
@layer components {
  .btn {
    background: #059669;
    border-radius: 8px;
  }
}
💡 From Experience
If you find yourself reaching for !important, it usually means you're retrofitting priority onto a codebase that wasn't designed with it. @layer lets you design priority upfront — and then !important almost never comes up.

② Unlayered styles beat everything

Styles that don't belong to any @layer sit above all layers in the cascade. This is a useful escape hatch — for urgent one-off overrides, just write outside any layer.

Unlayered styles win automatically
@layer base, components;

@layer components {
  .alert { color: white; }
}

/* No @layer = beats all layers */
.alert.urgent { color: red; }

③ NG vs. OK — the before-and-after

❌ The old specificity war
/* Library selector (high specificity) */
.ui-lib .btn.primary { background: blue; }

/* Your style (low specificity, loses) */
.btn { background: green; } /* Ignored */

/* Desperate !important */
.btn { background: green !important; }
✅ With @layer — no war needed
@layer vendor, components;

@layer vendor {
  .ui-lib .btn.primary { background: blue; }
}

@layer components {
  /* Low specificity, but higher layer = wins cleanly */
  .btn { background: green; }
}

Going Further — A Four-Layer Design System

For larger projects, @layer maps naturally onto the layered CSS architectures that teams like ITCSS and FLOCSS have been describing for years. Tokens → Base → Components → Utilities — each has a clear job and a clear priority.

Four-layer design system structure
/* Declare the full order once */
@layer tokens, base, components, utilities;

@layer tokens {
  :root {
    --color-primary: #059669;
    --radius-md: 8px;
  }
}

@layer base {
  *, *::before, *::after { box-sizing: border-box; }
  body { font-family: system-ui, sans-serif; }
}

@layer components {
  .btn {
    background: var(--color-primary);
    border-radius: var(--radius-md);
    padding: 8px 20px;
    color: white;
  }
}

@layer utilities {
  .mt-0 { margin-top: 0; }
  .hidden { display: none; }
}

Browser support is solid across the board — Chrome, Firefox, Safari, and Edge all support @layer in their current stable versions. For any modern project, it's ready to use today.

Takeaways

  • @layer organizes styles into named priority tiers — later layers win, regardless of selector specificity.
  • Declaring @layer base, components, utilities; at the top of your CSS sets the priority order for the entire file.
  • Trapping third-party libraries in a low-priority layer means your styles always win — no !important needed.
  • Styles written outside any @layer beat all layers — useful as an intentional escape hatch.
  • Tokens → Base → Components → Utilities is a solid four-layer structure for design system CSS.
  • Fully supported in all modern browsers. Safe to use in production today.