DEV Community

Franklin
Franklin

Posted on

Field Notes: Watching the Cascade Resolve, Start to Finish

Thursday's piece described the order the browser resolves conflicting CSS in: origin and importance first, then layer order, then specificity, then source order, with inheritance underneath all of it. Describing an order is one thing. Watching it actually run, on a real conflict, is another. This is that.

Also available in Español

The Setup

One button. Six declarations, all fighting over the same property, spanning every stage of the resolution sequence:

/* UA stylesheet — browser default */
button { color: buttontext; }

/* Author, @layer base (declared first) */
@layer base {
  .btn { color: black; }
}

/* Author, @layer utilities (declared after base) */
@layer utilities {
  .text-blue { color: blue; }
}

/* Author, unlayered */
#submit { color: red; }

/* Author, !important */
@layer base {
  .btn { color: purple !important; }
}

/* User stylesheet — e.g. a browser accessibility override */
button { color: green !important; }
Enter fullscreen mode Exit fullscreen mode
<button id="submit" class="btn text-blue">Submit</button>
Enter fullscreen mode Exit fullscreen mode

Which color wins?

Stage One: Origin and Importance

This stage is checked before anything else on the list is even consulted, and it ends the conflict immediately: green wins.

Not because it's the most specific selector. It isn't button alone is about as unspecific as a selector gets. It wins because user-origin !important outranks author-origin !important, which is the one place the usual assumption author beats user inverts.

Worth asking why the platform is built this way at all, because the reasoning is short and solid: a user sometimes needs a way to override author styles that work against them a site that sets text too small to read, or a color combination that fails their contrast needs. If author !important could always beat user !important, that override would be permanently unenforceable, no matter how the user's browser or assistive technology was configured. Accessibility settings need to stay authoritative. So the cascade gives user !important the final word, specifically so that guarantee holds.

That's not an edge case buried in the spec. It's the entire reason this stage of the algorithm exists.

Stage Two: Layer Order

Remove the two !important declarations the user override and the author override and look at what's left: four normal-importance declarations, spread across the UA stylesheet, two authored layers, and one unlayered rule.

Layer order settles this before specificity is ever reached. The unlayered #submit { color: red; } beats every layered rule, unconditionally unlayered styles always win over layered ones, regardless of how heavy or light the selector inside a layer happens to be. red wins this round.

Set the unlayered rule aside for a moment, and the comparison gets more interesting: .btn in base versus .text-blue in utilities. utilities was declared after base, so .text-blue would have won not because blue has a more specific selector than black (it doesn't; both are single classes), but because the layer it lives in was declared later. This is the exact mechanism from the Cascade Layers piece, now running against real other stages instead of standing on its own.

Stage Three: Source Order, in Isolation

Strip the example down one more time same layer, same specificity, two declarations differing only in which one appears later in the file:

@layer base {
  .btn { color: black; }
  .btn { color: navy; }
}
Enter fullscreen mode Exit fullscreen mode

navy wins. Nothing else was available to decide it same layer, same specificity so the algorithm falls through to the last stage: whichever rule appears later in source order. This is the stage article 6 named but never demonstrated. Here it is, isolated, doing exactly what it's specified to do and nothing more.

What This Actually Shows

Six declarations. Five different stages of one sequence, each one only getting consulted because everything before it failed to produce a decision. The moment origin and importance settled the outer example, layers, specificity, and source order never got asked anything at all they were irrelevant the instant green won. That's the real shape of the algorithm: not a tiebreaker sequence, but a series of gates. Most conflicts never make it past the first one that actually applies.

Top comments (0)