Lesson 14
The Cascade - The Complete Resolution Algorithm
Everything you've learned - layers, specificity, selectors, custom properties, inheritance - is part of one unified system: the cascade. This lesson puts it all together into a single mental model you can use to predict exactly which style wins in any situation.
The cascade answers one question
When multiple CSS declarations target the same property on the same element, which one applies? The cascade resolves this by checking a series of criteria in order. The first criterion to produce a winner ends the comparison.
Mental model: Think of it as a tournament bracket. Competing declarations are eliminated round by round. Each round is a different criterion. Most conflicts are resolved in the early rounds - you rarely need to go all the way to the end.
The resolution order - all 8 steps
When two or more declarations compete for the same property on an element, the browser resolves them in this order (first decisive step wins):
1. Origin & Importance
Where did the style come from, and is it !important?
Priority (highest → lowest): transition declarations > user-agent !important > user !important > author !important > author normal > user normal > user-agent normal
2. Inline styles
style="..." on the element beats any selector-based rule (within the same origin/importance tier).
3. Layers
Unlayered > last declared layer > ... > first declared layer. For !important: inverted (first layer wins).
4. Specificity
Compare selector specificity: (ID count, class/attribute/pseudo-class count, element/pseudo-element count). Higher wins.
5. Scope proximity (new, with @scope)
Closer scope root wins. Only relevant when using @scope.
6. Source order
Last declaration in source order wins. This is the final tiebreaker.
In practice: Most conflicts you encounter are resolved at step 3 (layers) or step 4 (specificity). If you use layers properly, you'll rarely even need to think about specificity. If you don't use layers, specificity is your main battleground.
Step 1: Origin & Importance - who wrote it?
CSS comes from three "origins":
- User-agent - the browser's default stylesheet (why buttons look different in Chrome vs Firefox)
- User - styles set by the user (accessibility overrides, custom stylesheets - rare in practice)
- Author - your stylesheets. This is almost always what's competing with itself.
Within author styles, !important flips the priority:
/* Normal author styles - resolved by later steps (layers, specificity, order) */
.card { color: blue; }
/* !important author styles - beat ALL normal author styles regardless of specificity */
.text { color: red !important; }
/* Even this loses to !important above, despite higher specificity: */
#main .content .card .title { color: green; } /* loses to red !important */
Why !important is a last resort: It jumps an entire tier in the cascade. Once you use it, the only way to override it is with another !important in a higher-priority layer or a later source order position. This creates escalation wars. Use layers instead.
Step 2: Inline styles
The style="" attribute on an element beats any selector-based rule in the same origin/importance tier:
The inline style (red) beats the stylesheet rule (blue), despite .card being a valid selector
<!-- HTML -->
<div class="card" style="color: red;">I'm red</div>
/* This loses to the inline style: */
.card { color: blue; }
/* Only !important can beat inline: */
.card { color: blue !important; } /* this WOULD win */
Step 3: Layers (you learned this in Lesson 11)
Quick recap of how layers interact with the rest of the cascade:
/* Resolution order for normal styles: */
Unlayered styles (no @layer) ← WINS over all layers
@layer utilities { } ← wins over components
@layer components { } ← wins over base
@layer base { } ← wins over reset
@layer reset { } ← lowest priority
/* For !important: INVERTED */
@layer reset { !important } ← WINS (highest priority)
@layer base { !important }
@layer components { !important }
@layer utilities { !important }
Unlayered !important ← lowest for !important
Step 4: Specificity - the scoring system
Specificity is a three-part score written as (A, B, C). Each position counts a different category of selector part:
Specificity = ( A, B, C )
↑ ↑ ↑
count of count of count of
#id .class element
[attribute] ::pseudo-element
:pseudo-class
(:hover, :not, :has, :is)
Calculate it by counting each part of your selector and placing the count in the right column:
h1 → 0 IDs, 0 classes, 1 element = (0, 0, 1)
.card → 0 IDs, 1 class, 0 elements = (0, 1, 0)
.card .title → 0 IDs, 2 classes, 0 elements = (0, 2, 0)
#main .card → 1 ID, 1 class, 0 elements = (1, 1, 0)
#main .card .title:hover → 1 ID, 3 (card+title+hover), 0 = (1, 3, 0)
div.card[data-v] → 0 IDs, 2 (class+attr), 1 elem = (0, 2, 1)
/* Functional pseudo-classes - specificity from their argument: */
:is(.card, #main) → (1, 0, 0) /* takes highest argument (#main) */
:where(.card, #main) → (0, 0, 0) /* :where is ALWAYS zero */
:not(.disabled) → (0, 1, 0) /* specificity of its argument */
Comparison rule: Read left to right. The first column where one score is higher wins. Columns don't add up across positions - (1, 0, 0) beats (0, 99, 99) because one ID outranks any number of classes. Think of it like version numbers: 2.0.0 > 1.99.99.
The scoring rules:
- Compare left to right: IDs beat any number of classes. Classes beat any number of elements.
(1, 0, 0)beats(0, 99, 99)- one ID outweighs 99 classes.- If tied at every position, fall through to step 6 (source order).
Step 6: Source order - the final tiebreaker
If origin, importance, layers, and specificity are all equal, the last declaration in source order wins:
Same specificity, same layer - last one in the file wins (coral)
<!-- HTML -->
<div class="box">What colour am I?</div>
/* Same specificity (0,1,0) - source order decides */
.box { background: blue; } /* first - loses */
.box { background: coral; } /* last - wins */
This is why CSS files loaded later in the <head> override earlier ones - and why putting your custom stylesheet after a framework's stylesheet lets you override it (assuming equal specificity).
Inheritance - the other way properties get values
The cascade resolves conflicting declarations. But what about properties with NO declaration at all? That's where inheritance comes in.
Some properties inherit by default - if not explicitly set, they take the parent's computed value:
- Inherited:
color,font-family,font-size,line-height,letter-spacing,text-align,visibility,cursor, custom properties (unless@propertysaysinherits: false) - NOT inherited:
background,border,padding,margin,width,height,display,position,overflow
The mental model: "Text-related" properties generally inherit (because you want text styling to flow down). "Box-related" properties don't (because you don't want every child to have the parent's padding).
Controlling inheritance explicitly
.child {
color: inherit; /* force inheritance (even for non-inheriting properties) */
border: initial; /* reset to the property's default value */
padding: unset; /* = inherit for inheriting properties, initial for non-inheriting */
all: revert; /* reset ALL properties to browser default (user-agent stylesheet) */
all: revert-layer; /* reset to the previous layer's value */
}
Putting it all together - a worked example
Which colour wins? Walk through the cascade steps.
<!-- HTML -->
<p id="intro" class="text highlight" style="color: pink;">What colour?</p>
/* Competing declarations for color on this element: */
@layer base {
p { color: gray; } /* (0,0,1) in base layer */
}
@layer components {
.text { color: blue; } /* (0,1,0) in components layer */
#intro { color: red; } /* (1,0,0) in components layer */
}
.highlight { color: teal !important; } /* (0,1,0) unlayered !important */
/* Resolution:
Step 1: !important beats all normal rules → "teal !important" vs "pink" (inline)
!important author beats inline (unless inline is also !important)
→ teal wins.
Without the !important, inline "pink" would win (step 2).
Without inline, the layer step would matter (unlayered > components > base).
Within the same layer, #intro (1,0,0) beats .text (0,1,0).
*/
The complete picture - one diagram
The practical takeaway: If you use @layer properly, you almost never need to think about specificity. If you keep specificity flat (BEM or similar), you almost never need to think about source order. The cascade gives you multiple tools - you only need to master the level of control your project requires.
Retrieval check
Question 1
In the cascade resolution order, what is checked BEFORE specificity?
Question 2
An element has style="color: blue" and a stylesheet rule .card { color: red !important }. Which wins?
Question 3
Selector A has specificity (0, 3, 0). Selector B has specificity (1, 0, 0). Same layer, same origin. Which wins?
Question 4
A property has no declaration on an element. It's an inheriting property (like color). What value does it get?
Question 5
Two rules in the same layer have the same specificity. How is the winner determined?