Why Your Rule Did Not Apply
Two rules target the same element and one of them wins, by a procedure that is completely deterministic and almost never taught. Score two selectors against each other and find out why the one you wrote lost.
Worth reading first: Choosing What to Style
Two rules target the same element and one of them wins, by a procedure that is completely deterministic and almost never taught. Score two selectors against each other and find out why the one you wrote lost.
Three questions decide every conflict, in order
The Cin CSS is Cascading, and this is what it names: the procedure for resolving conflicts. It is not vague, it is not a heuristic, and it is not "whatever comes last".
- 1
Is one of them !important?
An !important declaration beats every normal one regardless of anything else. If both are, the comparison continues on to the next question between them.
- 2
Which has higher specificity?
Count the selector's IDs, classes, and types. Higher wins. This is where most conflicts are actually settled, and it is the part people skip.
- 3
Which is written later?
Only if the first two are exact ties. Later in the file, or in a later file, wins. This is the tie-breaker — not the rule.
Almost everybody learns step three first and stops there. That is why "my rule is at the bottom and it still does not work" feels arbitrary: source order never got consulted, because step two had already decided.
Specificity is counted, not weighed
Specificity is three numbers, counted from the selector itself. Nothing about the declaration matters, only the selector in front of it.
IDs. Every # in the selector.
Classes, attribute selectors ([type="text"]), and pseudo-classes (:hover, :nth-child).
Element types (p, div, a) and pseudo-elements (::before).
The whole selector, including everything in the descendant chain. * and combinators count for nothing.
p 0-0-1.intro 0-1-0p.intro 0-1-1nav ul li a 0-0-4.nav .item a 0-2-1#header 1-0-0#header .nav a:hover 1-2-1* 0-0-0p.note
type + class
0
IDs
1
classes
1
types
.card .title .text
three classes
0
IDs
3
classes
0
types
Wins
.card .title .text wins on classes
The columns are compared left to right and the comparison stops at the first difference. Everything to the right of that column is never looked at — which is why one ID beats any number of classes, and why .card .title .text (0-3-0) loses to #intro (1-0-0).
This is not base-10 arithmetic. 0-1-11 does not beat 0-2-0 — the classes column is compared first, 1 is less than 2, and the eleven types are irrelevant. Thinking of it as a number is how people conclude specificity is unpredictable.
The crucial rule is the one the widget demonstrates: the columns are compared left to right and the comparison stops at the first difference. It is not a three-digit number.
This is why
#header (1-0-0) beats .a .b .c .d .e .f (0-6-0). One ID, and the classes column is never reached.
And this
.card (0-1-0) beats body main article section p (0-0-5). One class beats any number of element names.
An inline style attribute sits above all three columns, which is the practical reason inline styles are hard to work with: nothing in your stylesheet can override one except !important.
Source order only breaks an exact tie
When specificity is identical, the later declaration wins. This is genuinely useful and it is what makes an override file work.
.btn { background: grey; }.btn { background: green; } /* green: same 0-1-0, written later */#save .btn { background: green; } /* 1-1-0 — wins */.btn { background: grey; } /* 0-1-0 — loses, despite being later */Two practical consequences. Link stylesheets in the order you want them to override — a theme file after a base file. And within one file, put general rules first and specific ones after, so ties resolve the way you would expect while reading.
Inheritance is a separate mechanism entirely
Inheritance is not the cascade and the two are constantly confused. The cascade decides between rules that target the same element. Inheritance passes values down the tree to elements no rule targeted at all.
body { font-family: system-ui, sans-serif; color: #333; line-height: 1.6;}/* Every paragraph, list item, heading and link inside inherits all three — no rule mentions them. */Text-related properties: color, font-family, font-size, font-weight, line-height, text-align, letter-spacing, list-style, visibility, cursor.
Box-related properties: margin, padding, border, width, height, background, display, position. A paragraph does not get its parent's border.
Text properties are almost always wanted throughout a subtree; box properties almost never are. Inheriting a border would put one on every descendant.
value: inherit takes the parent's. initial resets to the CSS default. unset does whichever of those applies to that property.
Two things this explains. Links do not inherit colour from body, because the browser's own a { color: … }rule targets them directly, and a rule always beats an inherited value. And form controls do not inherit your font at all — they take the operating system's, which is why forms often look out of place until you fix it:
input, textarea, select, button { font: inherit;}!important is a debt, not a tool
Adding !important to a declaration lifts it above every normal one, whatever its specificity.
.btn { background: green !important; }It works. That is the problem: it works immediately, so it is reached for whenever a rule does not apply, and it removes the pressure to find out why.
What it costs
The next override needs its own !important, and then you are comparing important declarations by specificity — the same problem one level up, with a smaller escape hatch left. Stylesheets do genuinely reach the point where every rule has one.
When it is fair
Overriding a third-party stylesheet you cannot edit. A utility class that must always win, like a .hidden helper. Both are deliberate, both are rare, and both deserve a comment saying why.
Key takeaways
- Conflicts resolve in order: !important, then specificity, then source order.
- Source order is the last tie-breaker, not the rule — which is why "but mine is at the bottom" fails.
- Specificity is three counts: IDs, then classes/attributes/pseudo-classes, then types.
- Columns compare left to right and stop at the first difference. It is not a three-digit number.
- One ID beats any number of classes; one class beats any number of element names.
- An inline style attribute outranks all three columns.
- Inheritance is not the cascade: it passes text properties down to elements no rule targeted.
- Text properties inherit; box properties do not. Form controls inherit nothing until you write font: inherit.
- A rule always beats an inherited value, which is why links ignore body's colour.
- !important buys a fix now and an escalation later. Devtools shows you which rule actually won.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.