CSS The Cascade
The cascade is the algorithm the browser uses to decide which CSS declaration actually gets applied when more than one rule targets the same property on the same element. It is the “C” in CSS, and understanding it is what separates developers who fight their stylesheets from those who can predict exactly what will render before the page even loads. This lesson walks through every stage of the cascade in order, with worked examples showing exactly why one rule beats another.
The cascade is different from inheritance, which is a separate mechanism where certain properties (like color or font-family) pass down from parent to child elements automatically. The cascade only decides between competing declarations that explicitly target the same element; inheritance decides what happens when nothing targets an element directly. This lesson focuses on the cascade.
Overview: How the Cascade Works
Every time the browser paints an element, it gathers every declaration from every stylesheet that could apply to that element’s property. There can be dozens: the browser’s own default stylesheet, a CSS reset, a third-party framework, and your own author styles, all potentially setting the same property. The cascade sorts all of these candidate declarations using a strict, ordered set of tie-breaking rules, and whichever declaration comes out on top after all the tie-breaks is the one the browser renders.
The sort happens in four stages, applied in order, where the browser only moves to the next stage if the current one ends in a tie:
- Origin and importance — where the rule came from (browser default, user, or author stylesheet) and whether it is marked
!important. - Cascade layers — if the author has organized styles into
@layerblocks, layers declared later win over layers declared earlier, before specificity is even considered. - Specificity — a numeric weight calculated from the selector itself (IDs beat classes, classes beat elements, and so on).
- Order of appearance — if everything above is exactly tied, the declaration that appears later in the source (or in a later linked stylesheet) wins.
Crucially, specificity is a selector-level concept, not a whole-rule concept, and it only ever gets compared when origin/importance and layer are tied. A common misconception is that “the last rule in the file always wins” — that is only true when specificity is also tied. A highly specific selector defined at the very top of a stylesheet can still beat a low-specificity selector defined at the very bottom.
Syntax: The Cascade Sort Order
| Stage | What it compares | Who wins |
|---|---|---|
| 1. Origin & importance | User-agent, user, or author stylesheet; presence of !important |
Author !important beats author normal; user-agent styles lose to both unless marked important |
| 2. Layers | @layer declaration order |
Later-declared layer wins (unlayered author styles beat all layered ones) |
| 3. Specificity | ID / class / element counts in the selector | Higher specificity value wins |
| 4. Source order | Position in the document/stylesheet | The declaration that appears later wins |
Here is the simplest possible cascade conflict: two rules with identical selectors, so the tie is broken purely by source order.
/* Rule A */
p {
color: blue;
}
/* Rule B, appears later in the same file */
p {
color: red;
}
Result: Paragraph text renders red. Both rules have the exact same selector (p), so origin, layer, and specificity are all tied, and the cascade falls through to source order — Rule B was declared later, so it wins.
Examples
Example 1: Source order breaks a specificity tie
.notice {
border: 1px solid gray;
background: #f5f5f5;
}
.warning {
border-color: orange;
background: #fff3e0;
}
Applied to <div class="notice warning">Careful!</div>.
Result: The box has an orange border and a pale orange background. Both .notice and .warning are single-class selectors, so their specificity is identical. Because .warning is declared after .notice in the stylesheet, its border-color and background values win for the properties they both set.
Example 2: Specificity overrides source order
#main-content p {
color: navy;
}
p {
color: crimson;
}
Result: A paragraph inside an element with id="main-content" renders in navy, even though the crimson rule appears later in the file. #main-content p combines an ID selector (worth 100) and an element selector (worth 1), giving it a specificity of 101. The lone p selector is worth only 1. Because specificity is compared before source order, the higher-specificity rule wins regardless of which one was written last.
Example 3: !important and cascade layers
@layer base, theme;
@layer base {
a {
color: blue;
}
}
@layer theme {
a {
color: green !important;
}
}
a {
color: purple;
}
Result: Links render green. Normally, an unlayered rule (the final a { color: purple; }) beats any layered rule, no matter how the layers are ordered. But !important flips that priority for the declaration it is attached to: important declarations in layered styles beat unlayered normal declarations, and among competing important declarations, the layer declared first wins (the opposite order from normal declarations). Since only the theme layer’s rule is marked important here, it wins outright.
How the Browser Resolves Conflicts Step by Step
Consider this stylesheet applied to <button id="submit" class="btn primary">Send</button>:
button {
background: gray;
}
.btn {
background: silver;
}
.btn.primary {
background: dodgerblue;
}
#submit {
background: seagreen;
}
The browser resolves the background property for this button as follows:
- Step 1 — Collect candidates. All four rules match the element, so all four are candidates for the
backgroundproperty. - Step 2 — Check origin and importance. All four declarations are normal (non-important) author-origin rules, so this stage ties.
- Step 3 — Check layers. None of the rules are inside an
@layerblock, so this stage also ties. - Step 4 — Calculate specificity.
buttonscores (0,0,1);.btnscores (0,1,0);.btn.primaryscores (0,2,0);#submitscores (1,0,0). Specificity is compared column by column, ID count first — so#submit, with a 1 in the ID column, automatically beats every selector that has a 0 there. - Step 5 — Apply the winner.
#submithas the highest specificity, so the button renders with aseagreenbackground, even though.btn.primaryis more specific than the other two class-based rules and appears later in the file.
Common Mistakes
Mistake 1: Reaching for !important to “win” a conflict
.card-title {
font-size: 18px !important;
}
This works in isolation, but it is a trap: once one declaration is marked important, the only way to override it later is with an even more specific important declaration (or one in a higher-priority layer), which pushes teams into an “important arms race.” Prefer fixing the root cause — usually a selector that is more specific than it needs to be somewhere else in the codebase.
.card .card-title {
font-size: 18px;
}
Raising the intended rule’s specificity slightly (or lowering the competing rule’s specificity) solves the conflict without introducing an important declaration that future developers will have to fight.
Mistake 2: Assuming the last rule in the file always wins
#nav a {
color: white;
}
/* Written later, but loses anyway */
.footer a {
color: black;
}
If both selectors happen to match the same link (an unlikely but real scenario with nested markup), the #nav a rule wins even though .footer a was written afterward, because an ID-containing selector has higher specificity than a class-containing one. Source order is only the final, fourth tie-breaker — it never overrides a specificity difference.
.footer a {
color: black;
}
#nav a {
color: white;
}
Reordering the rules changes nothing here, which is exactly the point: to genuinely give .footer a priority, its specificity must be raised (or the competing selector’s lowered), not just its position in the file.
Best Practices
- Keep selectors as low-specificity as possible for as long as possible; reach for a single class rather than an ID or a long descendant chain.
- Avoid
!importantin regular application code; reserve it for narrow, well-documented exceptions like utility classes or overriding third-party CSS you cannot edit. - Use consistent selector patterns (like a single class per component) so that specificity stays predictable across the whole codebase.
- When you do need to guarantee style priority in a large codebase, prefer
@layerto organize precedence explicitly rather than stacking specificity or important declarations. - Use browser DevTools’ “Computed” and “Styles” panels to see exactly which rule the cascade picked and which ones were crossed out as losers.
- Write CSS in a logical layer order (resets, base elements, components, utilities) so source order naturally supports the cascade instead of fighting it.
Practice Exercises
- Exercise 1: Given
p { color: black; }and, later in the same file,article p { color: teal; }applied to a paragraph inside an<article>, which color renders and why? - Exercise 2: Write two rules, one using a single class selector and one using two chained class selectors on the same element, that both set
background-colorto different values. Predict which one wins before testing it, then explain the specificity of each. - Exercise 3: Rewrite a stylesheet that currently uses
!importantto force a link color, using a slightly more specific selector instead so the important declaration can be removed entirely.
Summary
- The cascade is the algorithm that resolves conflicts between multiple declarations targeting the same property on the same element.
- It resolves conflicts in four ordered stages: origin/importance, cascade layers, specificity, then source order — each stage only matters if the previous one ties.
!importantflips normal priority within its origin, but should be used sparingly because it is hard to override cleanly.- Specificity is calculated from ID count, class/attribute/pseudo-class count, and element/pseudo-element count, compared in that order.
- Source order is the last tie-breaker, not the first — a more specific rule earlier in the file still beats a less specific rule written later.
- The cascade is distinct from inheritance, which governs how unset properties pass from parent to child elements.
