Have you ever written a CSS rule and wondered why it had no effect? The answer almost always lies in the cascade โ the algorithm browsers use to decide which rule wins when multiple rules target the same element and property. Understanding the cascade, specificity, and inheritance turns CSS debugging from guesswork into a systematic process. In this lesson you will learn all three systems and how to write CSS that stays predictable.
The Three Pillars: Cascade, Specificity, Inheritance
1 โ The Cascade
When two rules conflict, the browser resolves it in order:
- Origin and importance โ browser default < author stylesheet;
!importantflips this - Specificity โ more specific selector wins
- Source order โ equal specificity: later rule wins
- Inheritance โ no rule applies: inheritable properties flow from parent
2 โ Specificity Scoring
| Selector Type | Score | Example |
|---|---|---|
Inline style="" |
(1,0,0,0) | <p style="color:red"> |
| ID selector | (0,1,0,0) | #header |
| Class / attribute / pseudo-class | (0,0,1,0) | .btn, [type], :hover |
| Element / pseudo-element | (0,0,0,1) | p, ::before |
| Universal, combinators | (0,0,0,0) | *, >, + |
3 โ Inheritance
| Properties That Inherit | Properties That Do NOT | Override With |
|---|---|---|
color, font-size, font-family |
margin, padding, border |
inherit keyword |
line-height, letter-spacing |
background, width, height |
initial to reset |
text-align, visibility |
display, position, overflow |
unset for either |
!important you add requires another !important to override later โ creating a specificity arms race. Reserve it strictly for utility classes or overriding unavoidable third-party inline styles.Basic Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Cascade & Specificity</title>
<style>
/* (0,0,0,1) โ element */
p { color: #64748b; font-family: system-ui, sans-serif; }
/* (0,0,1,0) โ class beats element */
.highlight { color: #1d4ed8; }
/* (0,0,1,1) โ class + element */
section .highlight { color: #16a34a; }
/* (0,1,0,0) โ ID beats all classes */
#special { color: #dc2626; }
/* Source order: later wins on equal specificity */
.box { background: lightblue; }
.box { background: lightyellow; }
/* Inheritance */
.parent-teal { color: teal; font-weight: bold; }
</style>
</head>
<body>
<p>Grey โ element (0,0,0,1).</p>
<p class="highlight">Blue โ class (0,0,1,0) beats element.</p>
<section>
<p class="highlight">Green โ section .highlight (0,0,1,1) beats .highlight.</p>
</section>
<p id="special" class="highlight">Red โ ID (0,1,0,0) beats class.</p>
<div class="box">Yellow โ source order decides equal specificity.</div>
<div class="parent-teal">
<span>Inherits teal color and bold weight.</span>
</div>
</body>
</html>
How It Works
Step 1 โ Element Selector Is the Baseline
p { color: #64748b; } scores (0,0,0,1) โ the weakest rule, easily overridden. It is the foundation for all paragraphs not targeted by anything more specific.
Step 2 โ Class Beats Element
.highlight scores (0,0,1,0). The class column beats the element column โ any paragraph with class=”highlight” turns blue regardless of source order.
Step 3 โ Compound Selector Adds Specificity
section .highlight scores (0,0,1,1) โ one class plus one element. The element column is now 1 vs. 0, so it beats .highlight alone.
Step 4 โ ID Wins Against Any Class
#special scores (0,1,0,0). The ID column immediately wins over any number of classes.
Step 5 โ Source Order Breaks Specificity Ties
Both .box rules share specificity (0,0,1,0). The tiebreaker is source order: the second declaration wins.
Real-World Example: Overriding Third-Party Styles
/* Library code you cannot edit (specificity 0,1,1,0): */
/* #app .ui-button { background: #e2e8f0; color: #1e293b; } */
/* โโ Approach 1: Match specificity + place after โโ */
#app .my-button {
background: #4f46e5;
color: white;
border-radius: 6px;
cursor: pointer;
}
/* โโ Approach 2: Add one extra class for higher specificity โโ */
.theme-custom .my-button {
background: #4f46e5;
color: white;
}
/* โโ Approach 3: Zero-specificity base styles with :where() โโ */
:where(button, .btn-reset) {
appearance: none;
background: transparent;
border: none;
padding: 0;
cursor: pointer;
font: inherit;
}
/* โโ currentColor keeps border in sync with text โโ */
.themed-icon {
color: #4f46e5;
border: 2px solid currentColor;
}
.themed-icon svg {
fill: currentColor;
width: 1em;
height: 1em;
}
Common Mistakes
Mistake 1 โ Using !important to fight specificity
โ Wrong โ leads to escalating arms race:
.btn { color: blue !important; }
.hero .btn { color: white !important; }
โ Correct โ raise specificity deliberately:
.btn { color: blue; }
.hero .btn { color: white; }
Mistake 2 โ Assuming all properties inherit
โ Wrong โ border does not inherit:
.parent { border: 2px solid red; }
/* children do NOT automatically get a border */
โ Correct โ use the inherit keyword explicitly:
.child { border: inherit; }
Mistake 3 โ Thinking source order overrides specificity
โ Wrong โ a class after an ID still loses:
#nav { color: blue; } /* (0,1,0,0) */
.nav { color: red; } /* (0,0,1,0) โ comes later but still loses */
โ Correct โ source order only breaks ties of equal specificity:
#nav.nav { color: red; } /* (0,1,1,0) โ now wins over #nav */
Quick Reference
| Concept | Key Rule | Example |
|---|---|---|
| Inline style | (1,0,0,0) โ highest authored specificity | style="color:red" |
| ID selector | (0,1,0,0) | #header |
| Class selector | (0,0,1,0) | .btn |
| Element selector | (0,0,0,1) | p, div |
| Source order | Tiebreaker when specificity is equal | Later rule wins |
!important |
Overrides all normal rules; avoid abuse | color: red !important; |
| Inheritance | Text props inherit; box/layout do not | color inherits; margin does not |