The Cascade, Specificity and Inheritance
IntermediateWhen two rules apply, one wins. Knowing exactly how that is decided is the difference between debugging CSS and adding !important until it works.
Overview
The C in CSS is the cascade, and it is the model that decides everything. When more than one rule targets an element, the browser resolves it in a fixed order: origin and importance first, then specificity, then source order. Almost every "my CSS is not applying" moment is a rule you did not know was more specific, and almost every !important is a developer who lost that fight without knowing what it was. Learning the algorithm — it is short — turns CSS from unpredictable into deterministic.
How a Winner Is Chosen
Four steps, in order. Later steps only run if the earlier ones tie.
/* 1. ORIGIN and IMPORTANCE
author !important > author > user agent (browser defaults)
2. SPECIFICITY — counted as (inline, id, class, element)
3. SOURCE ORDER — the last matching rule wins */
#main .card p /* (0,1,1,1) */
.card p.intro /* (0,0,2,1) */
p /* (0,0,0,1) */
.btn /* (0,0,1,0) */
style="color:red" /* (1,0,0,0) — beats every selector */
/* Compare left to right. A single id beats any number of classes:
(0,1,0,0) > (0,0,20,0) — specificity does not "add up" across
columns, which surprises people. */
/* Pseudo-classes count as classes; pseudo-elements as elements */
a:hover /* (0,0,1,1) */
li::marker /* (0,0,0,2) */
/* These three add nothing at all */
:where(.a, .b) /* (0,0,0,0) — always zero */
* /* (0,0,0,0) */
:is(#x) /* takes the HIGHEST of its arguments — careful */Keeping Specificity Low
The strategy that avoids the arms race in the first place.
/* An escalating war, one override at a time */
.card p { color: grey }
.page .card p { color: black }
#main .page .card p { color: navy }
#main .page .card p { color: red !important } /* the surrender */
/* Flat, single-class selectors instead — everything wins by order,
which is easy to reason about */
.card__text { color: grey }
.card__text--muted { color: silver }
/* :where() zeroes specificity, which is ideal for resets and
defaults you fully expect to be overridden */
:where(a) { color: var(--link) } /* any .class beats this */
/* Cascade layers give explicit priority regardless of specificity */
@layer reset, base, components, utilities;
@layer components { #sidebar .nav a { color: blue } }
@layer utilities { .text-red { color: red } } /* wins — later layer */
/* Utility frameworks like Tailwind sidestep the problem: every
class is (0,0,1,0), so order in the stylesheet decides. */
/* !important is legitimate in exactly two places: overriding a
third-party stylesheet you cannot edit, and utility classes
that must always win. */Inheritance
Some properties flow down the tree and some do not. Knowing which saves a lot of repetition.
/* Inherited — set once on a container */
color font-family font-size font-weight line-height
letter-spacing text-align visibility cursor list-style
/* NOT inherited — every element starts fresh */
background border padding margin width height display position
/* So this styles all descendant text */
.article { font-family: Georgia, serif; line-height: 1.6 }
/* And this does not cascade to children */
.article { border: 1px solid } /* only the container */
/* Explicit control */
.child { color: inherit } /* take the parent's value */
.child { all: unset } /* reset everything */
button { font: inherit } /* buttons do NOT inherit font
by default — a classic fix */
/* Custom properties DO inherit, which is what makes theming work */
:root { --text: #111 }
.dark { --text: #eee } /* everything inside picks it up */
/* Debugging: open DevTools, find the element, and read the Styles
panel top to bottom. Overridden rules are struck through, and the
winner is at the top. That panel answers the question faster than
any amount of guessing. */Key Points to Remember
- 1The cascade resolves in order: origin and importance, then specificity, then source order
- 2Specificity is compared column by column — one id beats any number of classes
- 3:where() has zero specificity and @layer sets priority explicitly, both of which prevent override wars
- 4Text properties inherit; box properties do not — and form controls need font: inherit explicitly
- 5The DevTools Styles panel shows the winner at the top with losers struck through, which answers most CSS questions instantly
Interview Questions
Sign in to ask AriaHow does the browser decide which of two conflicting CSS rules applies?
Why does #main .card p beat .card .intro p.text?
What does :where() do to specificity, and when is that useful?
Ask Aria about The Cascade, Specificity and Inheritance
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.