If you've ever fought with CSS where a style you're certain you wrote just refuses to apply — no typo, no missing semicolon, the rule is simply being ignored — the culprit is very often specificity, one of the least intuitive parts of how CSS decides which of several conflicting rules actually wins.
The problem specificity solves
A single HTML element can be targeted by more than one CSS rule at once. A paragraph might match a general p selector, a class selector like .intro, and an ID selector like #hero-text, all in the same stylesheet, each possibly setting a different color. The browser needs a consistent, predictable way to decide which of those competing rules actually wins, and that system is specificity.
How the scoring actually works
Specificity is calculated as three separate counts, compared in order rather than added into one combined number: the count of ID selectors, the count of class selectors (along with attribute selectors and pseudo-classes), and the count of element selectors (along with pseudo-elements). A selector with even one ID always beats a selector with any number of classes, no matter how many classes are stacked up, because the comparison happens category by category, left to right, rather than as a simple sum.
This is the part that trips people up: it feels natural to think a selector piling up five classes should out-muscle a single ID, since five feels bigger than one. But that's not how the comparison works. The ID category is checked first, and if one selector has more IDs than the other, it wins immediately regardless of what's in the class or element category.
Why this leads to escalating specificity wars
Once a developer discovers that adding more classes to a selector, or reaching for an ID, or (worst of all) sprinkling in !important makes a stubborn style finally take effect, it's tempting to keep using that same trick every time something doesn't apply as expected. The problem is that this creates an arms race: each fix makes future overrides harder, because now there's an even higher specificity bar to clear the next time someone needs to change that element's style. Codebases that have accumulated years of "just add another class" fixes often end up with deeply nested, overly specific selectors that are genuinely difficult to override without resorting to !important, which is close to the ceiling of the whole system and leaves almost no room to escalate further if a future override is ever needed.
A more sustainable approach
The more maintainable long-term fix is usually to keep selectors as flat and low-specificity as reasonably possible in the first place — favoring single classes over chained selectors, IDs, or deeply nested descendant selectors — and to rely on source order (later rules in the stylesheet naturally override earlier ones of equal specificity) rather than specificity escalation to control which style wins. Methodologies like BEM (Block Element Modifier) exist largely to solve this problem structurally, by giving every element a single, flat, purposefully-named class rather than relying on nested selectors or nested nesting to create the necessary specificity.
Inline styles and !important both sit above almost everything else in the cascade and should generally be treated as an emergency escape hatch rather than a normal tool — reaching for them regularly is usually a sign that the underlying selector structure needs rethinking rather than another override.
Debugging a specificity conflict
When a style genuinely isn't applying and you suspect specificity is the cause, browser developer tools are the fastest path to an answer — inspecting an element shows every matching rule, with overridden ones struck through, which usually makes the winning selector obvious immediately. If you want to understand a specific selector's raw specificity score without opening dev tools first, our CSS specificity calculator breaks any selector down into its ID, class, and element counts, which is often enough to spot exactly why one rule is beating another.
The short version
Specificity is compared category by category — IDs first, then classes, then elements — not added up as one combined score, which is why a single ID can beat a pile of classes. The healthiest long-term habit is keeping selectors flat and low-specificity from the start, using source order to manage simple overrides, and treating !important as a last resort rather than a routine fix, since it leaves very little room to maneuver the next time an override is genuinely needed.