Step 3 — Layers, Specificity, and the !important Inversion

Goal

Verify the cascade's real resolution order, and find the trap that makes a design system unoverridable.

Predict first

For each, which colour wins?

  1. #widget .btn { red } (1,1,0) vs .btn.primary { green } (0,2,0) — no layers
  2. @layer reset, base, app; with @layer app { .btn { blue } } (0,1,0) and @layer base { #widget .btn { red } } (1,1,0)
  3. @layer b { #widget .btn.primary { red } } vs an unlayered .btn { blue }
  4. @layer base, app; with @layer app { .btn { blue } } and @layer base { .btn { red !important } }

Case 4 is the one to think hardest about.

Run

npm run cascade

Expected output:

  specificity only (no layers)      -> RED    high-specificity #id rule wins
  layers: reset < base < app        -> BLUE   later LAYER wins despite lower specificity
  unlayered beats every layer       -> BLUE   unlayered = implicit final layer
  !important INVERTS layer order    -> RED    important reverses layer precedence

What just happened

The cascade resolves in this order — first difference decides:

  1. origin + importance
  2. layer order — later wins, and specificity is never consulted
  3. specificity
  4. source order

Case 2 is why layers matter. A rule with specificity 0,1,0 beat one with 1,1,0. Layer order is evaluated before specificity, so a design system can ship defaults in an early layer and applications override them with ordinary selectors — no specificity inflation, no !important, no :where() tricks.

Case 3 is the migration story. Unlayered styles form an implicit final layer, so anything you do not control — third-party widgets, unmigrated legacy CSS — outranks your entire stack. During migration that is a feature: legacy CSS keeps winning while new CSS is layered underneath.

Case 4 is the trap. Author !important reverses layer order. A design system that ships its defaults with !important becomes unoverridable by every consuming application — the exact opposite of the intent. That deserves a lint rule, not a convention.

Design your stack

@layer reset, tokens, base, components, patterns, utilities, overrides;
  • Declare the order once, loaded first.
  • Decide explicitly where unmigrated legacy CSS sits (it is unlayered, so: on top).
  • Ban !important inside system layers.
  • Specificity still matters within a layer — layers remove escalation, not discipline.

Checkpoint

docs/verification.md Checkpoints 4 and 5.