Every developer has hit this moment: you write a perfectly reasonable CSS rule, it doesn't apply, and the "fix" that shows up first in search results is !important. It works. You move on. Six months later, three more !important rules have piled up on the same element, and nobody — including past-you — remembers why.
If you've seen our Junior vs Senior dev reels, you already know where this is going.
The chart everyone's screenshotted, nobody applies
Most specificity explanations start with a chart: inline styles beat IDs, IDs beat classes, classes beat elements. True, but useless in the moment you actually need it — because the chart doesn't tell you what to do when you're staring at DevTools wondering why your rule lost.
How the browser actually decides
The browser isn't judging your code. It's running a deterministic scoring system, and every CSS rule gets scored on exactly three things, in this order:
- Origin and importance — user-agent styles lose to author styles, and
!importantrules jump the entire queue regardless of everything else. - Specificity — calculated as a tuple of (inline styles, IDs, classes/attributes/pseudo-classes, elements/pseudo-elements). Higher tuple wins, compared left to right.
- Source order — if the specificity tuple is a tie, whichever rule was declared last in the stylesheet wins.
That's the whole system. No exceptions, no magic. When a rule "should" apply but doesn't, it's because something else scored higher on one of those three axes — and the fix is finding that rule, not out-scoring it with !important.
A conflict, resolved without reaching for the hammer
/* styles/checkout.css */
#checkout .btn {
background: gray;
}
/* styles/button.css, loaded after checkout.css */
.btn-primary {
background: blue;
}
.btn-primary never wins here, no matter what order the files load in. #checkout .btn scores (0, 1, 1, 0) — one ID, one class. .btn-primary scores (0, 0, 1, 0) — one class. The ID-scoped rule wins on specificity before source order is even considered.
The !important fix looks like this:
.btn-primary {
background: blue !important;
}
It works, but it doesn't explain anything — it just skips the scoring system entirely. The actual fix is to either scope the override to match or beat the ID selector, or better: stop styling .btn inside an ID-scoped block in the first place.
.btn-primary {
background: blue;
}
/* styles/checkout.css — scope removed, class does the work */
.checkout-btn {
background: gray;
}
Once nothing is fighting the class selector on an unequal footing, the cascade behaves the way you'd expect without any !important at all.
The actual lesson
!important isn't wrong because it's "bad practice" in the abstract — it's wrong because it treats a symptom (my style isn't applying) without diagnosing the cause (something more specific is already applying). Every !important left in a codebase is a small, permanent flag that says "I didn't have time to find out why." That's fine under a deadline. It's not fine as a long-term habit, because the next person to touch that file — often you, a year later — inherits a cascade nobody fully understands anymore.
Next time a style won't apply, open DevTools, look at what's actually winning, and ask why — before reaching for the override that makes the question disappear instead of answering it.