EduFlow
Modern CSS Mastery
0%

Section 1 · The Cascade, for Real

Cascade Layers with @layer

18 min3 min readCode playground

Lesson at a glance

Organise a stylesheet into cascade layers so that whole groups of styles (reset, components, utilities) win by layer order instead of by specificity.

  1. 1Declare the order, then fill the layers
  2. 2Unlayered styles win
  3. 3Important declarations flip the order
  4. 4Nested layers and reverting
  5. 5Adopting layers in an existing codebase
  6. 6Your turn

No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.

Waypoint’s stylesheet has grown four kinds of CSS: a reset, base element styles, components like the session card, and one-job utilities like .bg-accent that the content team sprinkles into the HTML. Utilities are supposed to win; that’s their whole point. But a component selector such as .schedule .session-card .badge scores (0,3,0), and a utility scores (0,1,0), so the utility loses and someone writes !important again.

Specificity was never designed to express “this group of styles outranks that group”. Cascade layers were.

Declare the order, then fill the layers

A layer is a named bucket of rules. You announce the order once, at the top of your CSS:

styles.css
@layer reset, base, components, utilities;

Then you put rules into layers, in any order, from any file:

CSS
@layer components {
  .schedule .session-card .badge {
    background: #ede9fe;
    color: #5b21b6;
  }
}

@layer utilities {
  .bg-accent { background: #fde68a; }
}

Now .bg-accent wins over the component’s background, despite its lower specificity. When the cascade reaches step 3 (layers) it sees the two declarations come from different layers and picks the later-declared one. It never gets to specificity. Inside a single layer, specificity and source order work as you already know.

Layers stacked from lowest to highest priority: reset, base, components, utilities, and unlayered styles on top. An arrow on the right points upward labelled higher priority. @layer reset @layer base @layer components @layer utilities Unlayered styles higher priority
Later layers win for normal declarations; unlayered styles sit on top

The first @layer statement fixes the order. If you write @layer utilities { … } before ever declaring components, utilities becomes the first (lowest) layer. That’s why the order statement belongs at the very top of your entry stylesheet.

Unlayered styles win

Any normal rule that isn’t inside a layer goes into an implicit layer that beats all named layers. It feels backwards at first, but it’s what makes layers adoptable: you can wrap new code in layers today, and the existing unlayered CSS keeps its current behaviour.

The flip side is a classic trap.

Third-party files can go straight into a layer when you import them:

CSS
@layer vendor, reset, base, components, utilities;
@import url("calendar-widget.css") layer(vendor);

Note that @import rules must come before other rules except @charset and @layer statements, which is exactly why the order statement can sit above it.

Important declarations flip the order

For !important declarations, layer priority reverses: an important rule in reset beats an important rule in utilities, and important layered rules beat important unlayered ones. This mirrors how origins work (a user’s important rule beats yours) and gives low layers a way to protect something essential. A reset can lock [hidden] { display: none !important; } and no component can accidentally show hidden content.

Nested layers and reverting

Layers can nest: @layer components.cards { … } creates a cards layer inside components, and its priority is resolved inside its parent. Teams use this to give each component folder its own sub-layer without affecting global order.

One more keyword pairs with layers: revert-layer. Setting color: revert-layer rolls the property back to whatever the previous layers would have produced, as if this layer’s declaration didn’t exist. It’s handy in a utility such as .reset-color { color: revert-layer; }.

Adopting layers in an existing codebase

You don’t need a rewrite. On the Waypoint codebase, the migration took one afternoon and three steps:

  1. Add the order statement at the very top of the entry stylesheet, including a legacy layer first.
  2. Wrap every existing file in @layer legacy { … }, or import it with layer(legacy). Nothing changes visually, because the old rules still compete with each other exactly as before.
  3. As you touch a component, move its rules out of legacy and into components. Each move can only make your new code stronger relative to the old code, never weaker.

The pay-off arrives quickly: new components stop needing heavy selectors to beat old ones, and you can delete !important declarations one by one as their reason disappears.

Your turn

In the exercise, the Waypoint badge has a heavy component selector and a utility that should override it. Put the rules into layers so the utility wins without editing any selector or adding !important. Layers solve “which group wins”; the next lesson tackles “which value”, using custom properties as the dials your components expose.

Key takeaways

  • Declare your layer order once at the top with @layer reset, base, components, utilities; and later layers win over earlier ones regardless of specificity.
  • Specificity still matters, but only between declarations inside the same layer.
  • Normal styles that are not in any layer beat every layered style, so wrap legacy or third-party CSS in a layer instead of leaving it loose.
  • !important reverses layer order: an important declaration in an early layer beats an important one in a later layer.

Further reading

Check your understanding

4 questions

Question 1 of 4

Which background does .badge.sold-out get?

CSS
@layer components, utilities;
@layer utilities { .sold-out { background: gold; } }
@layer components { .schedule .card .badge { background: lavender; } }
Question 2 of 4

You add @layer reset, components; and wrap your code, but an old rule h2 { color: black; } sitting outside any layer still overrides your component headings. Why?

Question 3 of 4

How do you put a third-party stylesheet into the lowest-priority layer?

Question 4 of 4

In a components layer, a declaration says color: red !important. In a later utilities layer, another says color: blue !important. Which wins?

Practice

Code playground

Let the utility win with layers

The Sold out badge should be yellow (#fde68a) because it has the .bg-accent utility, but the component selector .schedule .session-card .badge out-scores it. Put the rules into cascade layers (reset, components, utilities, in that order) so utilities win. Keep every selector exactly as it is and don’t use !important. The plain Layout badge must keep its lavender component style.

<section class="schedule">
  <article class="session-card">
    <h2>Container Queries in Production</h2>
    <span class="badge">Layout</span>
    <span class="badge bg-accent">Sold out</span>
  </article>
</section>Loading the editor…

Ctrl/⌘ + Enter to run · Runs in a sandbox in your browser. Nothing leaves your device.

Preview

Console

(no output)

Checks

  • The Sold out badge is yellow
  • The Layout badge keeps its lavender component style
  • The CSS uses @layer blocks
  • The component selector is unchanged
  • No !important anywhere in the CSS