The cascade and specificity — which rule actually wins
When two CSS rules target the same element with conflicting values, which one wins is decided by a precise, calculable algorithm — not by which rule 'feels' more specific, and definitely not by which one was written more recently, alone.
3 min read
The order of tie-breakers, in the order they're actually checked
/* 1. !important — wins regardless of everything else below (use sparingly) */
/* 2. Inline style="..." attribute — beats any stylesheet rule */
/* 3. Specificity — a calculated score per selector, covered below */
/* 4. Source order — if specificity is EQUAL, the LAST rule declared wins */CSS's name for this whole resolution process is the cascade, and it runs through these tie-breakers in a fixed order — specificity is only even consulted once !important and inline styles are ruled out, and source order is only the deciding factor when specificity is genuinely tied. Most real-world "why isn't my CSS applying" confusion comes from skipping straight to "I'll just write it later in the file so it wins" without realizing a more specific selector earlier in the file can still beat it.
Calculating specificity: it's a score, not a feeling
p { color: blue; } /* specificity: (0, 0, 1) — one element */
.text { color: green; } /* specificity: (0, 1, 0) — one class */
#hero p { color: red; } /* specificity: (1, 0, 1) — one ID, one element */
/* On a <p class="text"> inside <div id="hero">, the color is RED —
an ID selector always outweighs any number of classes or elements */Specificity is calculated as a three-part score: (ID count, class/attribute/pseudo-class count, element/pseudo-element count) — compared left to right, like a version number, not added together as a single sum. One ID beats any number of classes; one class beats any number of bare elements; a selector with zero of a given category simply scores zero in that slot. This is precisely why #hero p beats .text here even though .text looks "more specific" by word count — IDs categorically outrank classes, full stop, regardless of how many classes are stacked.
Why reaching for !important usually makes the next bug worse
.button { color: blue !important; }
/* Now the ONLY way to override this anywhere else in the codebase
is another !important with equal-or-higher priority — normal
specificity rules no longer apply at all */!important doesn't participate in specificity at all — it short-circuits the cascade entirely, before specificity is even consulted. The real problem: once one rule uses it, overriding that rule anywhere else legitimately requires another !important, which starts an arms race that makes the stylesheet progressively harder to reason about. It's a legitimate escape hatch for a narrow set of real situations (overriding a third-party library's inline styles you can't edit) — but reaching for it to win an ordinary specificity fight is usually a sign the actual selector needs fixing, not silencing.
Specificity conflicts across separate stylesheets work exactly the same way
The cascade doesn't care whether two conflicting rules live in the same file or in entirely separate stylesheets (your own CSS vs. a component library's CSS, for instance) — it's the same specificity-then-source-order algorithm either way. This matters in practice because load order affects source-order tie-breaks across files: a stylesheet loaded later in the <head> wins ties against one loaded earlier, which is a real, common source of "the library's default styling isn't overriding the way I expected" bugs when stylesheet order gets rearranged.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. In what order does the cascade resolve conflicting CSS rules?
2. Why does `#hero p` beat `.text` even though `.text` might look shorter or 'more specific' by word count?
3. Why does reaching for `!important` to win an ordinary specificity fight tend to cause more problems later?