The Cascade, Specificity, and Inheritance
Prerequisites: Combinators
When several declarations set the same property on the same element, the cascade picks one. For everyday CSS, three things decide it, in this order:
- Importance and origin —
!importantdeclarations beat normal ones; author styles (yours) beat the browser's defaults. - Specificity — the more specific selector wins.
- Order — if specificity is equal, the declaration that appears last wins.
(Level 3 adds cascade layers and scoping to the picture.)
Specificity
Count the selector's parts in three columns:
| Column | Counts |
|---|---|
| A | id selectors (#main) |
| B | classes, attribute selectors, pseudo-classes (.card, [type], :hover) |
| C | type selectors and pseudo-elements (p, ::before) |
Compare column A first, then B, then C — like version numbers. * and
combinators count nothing.
| Selector | Specificity |
|---|---|
p |
0,0,1 |
nav a |
0,0,2 |
.card |
0,1,0 |
.card p |
0,1,1 |
a:hover |
0,1,1 |
#main .card |
1,1,0 |
style="..." attribute |
beats any selector |
One id beats any number of classes: #main (1,0,0) wins over
.a.b.c.d.e.f (0,6,0).
<style>
#intro { color: purple; }
.text.lead { color: green; }
.lead { color: blue; }
p { color: red; }
p { color: orange; }
</style>
<p id="intro" class="text lead">A</p>
<p class="text lead">B</p>
<p class="lead">C</p>
<p>D</p>
<script>
for (const p of document.querySelectorAll("p")) {
console.log(p.textContent, getComputedStyle(p).color);
}
</script>
A rgb(128, 0, 128)
B rgb(0, 128, 0)
C rgb(0, 0, 255)
D rgb(255, 165, 0)
- A is purple: the id wins over everything.
- B is green: two classes beat one.
- C is blue: a class beats a type selector.
- D is orange: two equal
prules — the later one wins.
!important
color: red !important; beats all normal declarations, whatever their
specificity. It's a sledgehammer: the only way to override it is another
!important with higher specificity. Reserve it for utility classes and
for overriding third-party CSS you can't change.
Inheritance
Some properties pass from parent to child automatically — mostly text
properties: color, font-*, line-height, letter-spacing,
text-align, visibility, cursor, list-style. Box properties —
margin, padding, border, background, width — are not
inherited.
<style>
.box { color: teal; border: 2px solid teal; font-size: 20px; }
</style>
<div class="box"><p>Inside</p></div>
<script>
const p = getComputedStyle(document.querySelector(".box p"));
console.log(p.color, p.fontSize, p.borderTopWidth, p.borderTopStyle);
</script>
rgb(0, 128, 128) 20px 0px none
The paragraph inherited the color and font size but not the border.
An inherited value always loses to a rule that targets the element
directly, however weak its selector — p { color: red } beats a color
inherited from #main.
Controlling inheritance
Every property accepts these keywords:
inherit— take the parent's value.initial— the property's initial value from the specification.unset—inheritfor inherited properties, otherwiseinitial.revert— back to the browser's default style.
button {
font: inherit;
color: inherit;
}
Form controls don't inherit fonts by default — this common reset makes them match the surrounding text.
Practice
Which color does the link get, and why?
<style>
#nav a { color: white; }
.menu-link { color: yellow; }
a.menu-link.active { color: orange; }
</style>
<nav id="nav"><a class="menu-link active" href="/">Home</a></nav>
Show solution
White. #nav a is 1,0,1; a.menu-link.active is 0,2,1; .menu-link is
0,1,0. The id makes #nav a the most specific, even though the other
rules come later. This is why ids in selectors cause trouble: to make
the active link orange, you'd have to write #nav a.active — replace
#nav a with .nav a instead.