Cascade, Specificity, and Inheritance Deep Dive
The Algorithm That Decides Every Pixel
You write color: red. The browser ignores it. Why? Because somewhere in an eight-level cascade algorithm, something else won — and it wasn't specificity. Most developers learn "specificity + source order" and call it a day. The reality is that specificity is round 6 of 8. Origins, context, layers, scope, and !important all come first. Once you see the full algorithm, you'll stop guessing why your styles don't apply and start knowing.
Think of the cascade as an eight-round elimination tournament. Two competing declarations enter, and at each round, one criterion is checked. The first criterion to produce a winner ends the match — later rounds are never reached. Most CSS conflicts are resolved by round 3 or 4. But when declarations are closely matched (same origin, same layer, same scope), you reach the later rounds where specificity and source order decide.
The Full Cascade Algorithm (CSS Cascading Level 5)
When multiple declarations target the same property on the same element, the cascade resolves the conflict in this order:
1. Origin and Importance
Declarations are sorted by their origin and whether they're !important:
| Priority (highest first) | Origin | Importance |
|---|---|---|
| 1 | Transition declarations | |
| 2 | User agent | !important |
| 3 | User | !important |
| 4 | Author | !important |
| 5 | Animation @keyframes | |
| 6 | Author | Normal |
| 7 | User | Normal |
| 8 | User agent | Normal |
Notice the inversion: for !important, user agent beats user beats author. For normal declarations, author beats user beats user agent. This protects user accessibility settings.
2. Context (Scoping Root)
In @scope rules, declarations from a narrower scope beat those from a wider scope.
3. Cascade Layers (@layer)
Within the same origin, declarations are sorted by layer order. Later-declared layers have higher priority:
@layer base, components, utilities;
/* utilities > components > base */
/* Unlayered styles beat ALL layers */
Unlayered author styles beat ALL layered author styles, regardless of the layer's position. If you put your library in a layer but your app code is unlayered, your app code always wins — which is exactly the intended behavior.
4. Inline Styles
style="" attributes have higher priority than any non-!important stylesheet declaration within the same origin.
5. Specificity
The (ID, CLASS, ELEMENT) tuple comparison, compared column by column.
6. Scoping Proximity
In @scope rules, declarations from a scope with fewer generational hops to the element have priority.
7. Source Order
The declaration that appears last in source order wins.
8. Default (Initial/Inherited Value)
If no declaration applies, the property uses its inherited value (if inherited) or its initial value.
Inheritance: Which Properties and Why
Not all CSS properties inherit. The distinction matters for understanding default behavior:
Inherited Properties (selected)
/* Typography */
color, font-family, font-size, font-weight, font-style,
letter-spacing, line-height, text-align, text-indent,
text-transform, white-space, word-spacing
/* Lists */
list-style, list-style-type, list-style-position
/* Visibility */
visibility, cursor
/* Other */
direction, writing-mode
Non-Inherited Properties (selected)
/* Box model */
margin, padding, border, width, height
/* Layout */
display, position, float, clear, overflow
/* Visual */
background, box-shadow, opacity, outline
/* Flex/Grid */
flex, grid, align-items, justify-content, gap
Controlling Inheritance
.child {
color: inherit; /* Force inheritance (even on non-inherited properties) */
border: inherit; /* Inherit border from parent (unusual but valid) */
padding: initial; /* Reset to the property's initial value */
margin: unset; /* inherited? → inherit. non-inherited? → initial */
display: revert; /* Reset to user-agent stylesheet value */
all: unset; /* Reset ALL properties to inherit/initial */
}
The difference between initial, unset, and revert
initial always resets to the CSS specification's initial value. For display, that's inline — not block, even on a <div>. unset checks if the property inherits: if yes, it inherits; if no, it uses initial. revert is the most useful — it resets to the user-agent stylesheet value. For a <div>, display: revert gives you block (the browser default), while display: initial gives you inline (the spec initial value).
Cascade Layers and the New Mental Model
Before layers, CSS architecture was a specificity management problem. With @layer, specificity becomes secondary to layer order:
@layer reset, base, components, utilities;
@layer reset {
/* Highest-specificity selectors here lose to any later layer */
#app main .hero h1.title { color: black; }
}
@layer utilities {
/* Lowest specificity wins over reset because it's in a later layer */
.text-red { color: red; } /* This wins */
}
Layer Organization Strategy
/* Import order defines layer priority */
@layer reset, vendor, base, components, overrides;
@layer reset {
@import 'reset.css';
}
@layer vendor {
@import 'some-library.css'; /* Contained — can't escalate */
}
@layer base {
/* Design tokens, typography, default element styles */
}
@layer components {
/* Component-specific styles */
}
@layer overrides {
/* Page-specific overrides — highest layered priority */
}
/* Unlayered styles (highest priority) for truly exceptional cases */
Production Scenario: Third-Party CSS Containment
/* Problem: Third-party library uses high-specificity selectors */
/* .ui-dialog .ui-dialog-content .message { color: red !important; } */
/* Solution: Put third-party CSS in a low-priority layer */
@layer vendor, app;
@layer vendor {
@import 'problematic-library.css';
/* Even their !important declarations are contained within the vendor layer */
}
@layer app {
.message { color: blue; } /* Wins over vendor layer */
}
| What developers do | What they should do |
|---|---|
| Thinking specificity is the main cascade criterion Specificity is criterion #5 in an 8-step algorithm. Most real-world conflicts are resolved earlier. | Origin, layers, and inline styles are all checked BEFORE specificity |
| Using display: initial expecting block behavior on a div initial resets to the spec initial value (inline), not the browser default (block) | Use display: revert to reset to the user-agent default (block for divs) |
| Assuming all properties inherit by default If margin inherited, every child would have the parent's margin — which would break all layouts | Only typography, color, and a few other properties inherit. Layout properties don't. |
| Putting your own styles in a cascade layer and expecting them to beat unlayered code The spec deliberately gives unlayered styles highest priority within the same origin. | Unlayered styles beat ALL layered styles. Put third-party code in layers, keep your overrides unlayered. |
- 1The cascade checks origin, context, layers, inline, specificity, proximity, source order — in that exact sequence
- 2Cascade layers are checked BEFORE specificity — layer order beats any specificity difference
- 3Unlayered styles beat all layered styles within the same origin
- 4Use revert (not initial) to reset to user-agent defaults — initial gives the spec default, not the browser default
- 5Only typography, color, visibility, and list-style properties inherit by default — layout properties don't