What you'll learn
Quick Answer
When two rules target the same element, the browser applies the one with higher specificity, scored as three numbers: ids, then classes and attributes and pseudo-classes, then elements and pseudo-elements. Compare left to right, and the first difference decides — one id beats any number of classes. Only if specificity ties does source order matter. Inline styles outrank everything except !important, which should be a last resort rather than a fix.
How Specificity Is Calculated
Specificity is three numbers, usually written as (a, b, c).
- a — ids. Count the
#idselectors. - b — classes, attributes and pseudo-classes.
.card,[type="text"],:hover,:nth-child(). - c — elements and pseudo-elements.
div,p,::before.
p (0,0,1)
.intro (0,1,0)
p.intro (0,1,1)
#header (1,0,0)
#header .nav a:hover (1,2,1)
* , inherited styles (0,0,0)Compare from the left. The first column that differs decides it, and nothing in a later column can compensate.
That is the part people find counter-intuitive: one id beats any number of classes. A selector with ten classes scores (0,10,0) and still loses to a single #id at (1,0,0). It is not a total — it is three separate comparisons in priority order.
Note also that the universal selector * and combinators like > and + add nothing, and :not() itself adds nothing though its argument counts.
Specificity Is Only One Step
Before blaming specificity, know the order the browser actually resolves in.
- Origin and importance. Author
!importantbeats author normal, which beats browser defaults. (User-agent!importantand user styles sit above author!important, which is why accessibility overrides can win.) - Inline styles. A
style=""attribute outranks any stylesheet rule of the same importance. - Specificity. The three-number comparison above.
- Source order. Only when everything above ties does the last rule win.
That last point matters. "The later rule wins" is only true at equal specificity. Moving a rule to the bottom of your stylesheet will not help if the competing rule is more specific — which is exactly the moment people give up and reach for !important.
One more rule that sits outside this: inheritance always loses to a direct match. A colour inherited from body has zero specificity, so any rule that targets the element itself wins, however weak.
Why !important Is a Trap
!important works, which is the problem. It resolves today's conflict by creating a worse one later.
.button { background: blue !important; }Now nothing can override that background through normal means. When a variant genuinely needs a different colour, the only tool left is another !important — and once two exist, they are compared by specificity among themselves, so the escalation continues. Large codebases reach a state where every rule shouts and none can be reasoned about.
It is also the hardest kind of CSS to debug, because devtools shows your correct-looking rule struck through with no obvious cause.
There are two legitimate uses. Overriding a third-party stylesheet you cannot edit, and utility classes in a framework that are meant to be final — Tailwind's approach, where .hidden should always hide. Outside those, treat it as a signal that the selector strategy needs fixing.
If you must use it, leave a comment saying why. Six months later nobody remembers, including you.
Debugging a Style That Will Not Apply
Work through this in order rather than guessing.
1. Open devtools and inspect the element. The Styles panel lists every matching rule in priority order, with overridden declarations struck through. If your rule appears struck through, it is a specificity or order problem. If it does not appear at all, the selector is not matching.
2. If the rule is absent, the selector is wrong. A typo, the wrong class name, or a structure assumption that does not hold — .card > p requires p to be a direct child.
3. If it is struck through, compare specificity with the rule that won, using the three numbers.
4. Check for inline styles. JavaScript that sets element.style.something writes an inline style, which beats your stylesheet. This catches people constantly with animation and UI libraries.
5. Check the property is inheritable or set on the right element. Setting text-align on a parent works because it inherits; setting border on a parent does not reach children.
6. Confirm the value is even valid. One invalid declaration is dropped silently while the rest of the block applies, so the rule looks half-broken.
Keeping Specificity Low on Purpose
The best fix for specificity wars is not winning them but avoiding them.
- Prefer classes. A flat vocabulary of single-class selectors keeps almost everything at (0,1,0), where source order is a predictable tiebreaker.
- Avoid ids for styling. Use them for anchors and JavaScript hooks. Their (1,0,0) weight cannot be beaten without another id.
- Do not over-nest.
.page .content .card .titlescores (0,4,0) and is hard to override. Sass nesting makes this easy to do accidentally — a good reason to keep nesting shallow. - Do not qualify unnecessarily.
div.cardis more specific than.cardand no more useful, while also locking the class to one element type. - Use
:where()for zero-specificity grouping. Everything inside:where()contributes nothing, which is ideal for base styles designed to be overridden.
:where(.prose) p { margin-block: 1em; } /* (0,0,1) — trivially overridable */
.prose p { margin-block: 1em; } /* (0,1,1) — harder to override */Methodologies like BEM exist mostly to enforce this: one class per component element means specificity stays flat and predictable across a whole codebase.
