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?
#widget .btn { red }(1,1,0) vs.btn.primary { green }(0,2,0) — no layers@layer reset, base, app;with@layer app { .btn { blue } }(0,1,0) and@layer base { #widget .btn { red } }(1,1,0)@layer b { #widget .btn.primary { red } }vs an unlayered.btn { blue }@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:
- origin + importance
- layer order — later wins, and specificity is never consulted
- specificity
- 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
!importantinside system layers. - Specificity still matters within a layer — layers remove escalation, not discipline.
Checkpoint
docs/verification.md Checkpoints 4 and 5.