CSS Specificity Explained: How Browsers Decide Which Styles Win
CSS specificity determines which style rule applies when multiple rules target the same element. Learn the specificity calculation algorithm, understand the cascade, and avoid common pitfalls like over-using !important.
What Is CSS Specificity?
When multiple CSS rules target the same element and set the same property, the browser must decide which rule wins. Specificity is the algorithm that determines this — each selector is assigned a numerical weight, and the rule with the highest specificity wins.
If two rules have equal specificity, the cascade applies: the rule that appears later in the stylesheet wins.
The Specificity Calculation
Specificity is calculated as a three-part value: (A, B, C)
- A = number of ID selectors (
#id) - B = number of class, attribute, and pseudo-class selectors (
.class,[attr],:hover) - C = number of element type and pseudo-element selectors (
div,::before)
These are compared from left to right (A first). A higher A value always beats a higher B or C value — no matter how many class selectors you have, they cannot "overflow" into A.
The universal selector *, combinators (+, ~, >, ), and the :is(), :not(), :has(), :where() pseudo-classes (with some exceptions) contribute 0 to specificity.
Examples
| Selector | (A, B, C) | Notes |
|---|---|---|
* | (0, 0, 0) | Universal selector |
p | (0, 0, 1) | Element |
p.intro | (0, 1, 1) | Element + class |
.nav .link | (0, 2, 0) | Two classes |
#header | (1, 0, 0) | ID |
#header .nav | (1, 1, 0) | ID + class |
style="" (inline) | (1, 0, 0, 0) | Inline styles — separate "column" above A |
!important | Overrides specificity | Nuclear option |
/* (0, 0, 1) */
p {
color: red;
}
/* (0, 1, 0) — wins */
.intro {
color: blue;
}
/* (1, 0, 0) — wins over both */
#main {
color: green;
}
Pseudo-classes and Pseudo-elements
:hover, :focus, :nth-child(), :not(), :is(), :has() are pseudo-classes (count as B).
::before, ::after, ::first-line are pseudo-elements (count as C).
:not(X) — the :not itself adds 0, but the argument X contributes its own specificity.
:is(X, Y) — takes the specificity of its highest-specificity argument.
:where(X) — always contributes 0 specificity (useful for resets and low-specificity utilities).
/* (0, 1, 1) — :hover is B, a is C */
a:hover {
color: red;
}
/* (0, 0, 0) — :where has zero specificity */
:where(p, div, section) {
margin: 0;
}
The !important Declaration
!important overrides specificity entirely. Rules marked !important are compared against other !important rules (by specificity), then against non-!important rules.
p {
color: red !important;
} /* wins over any non-!important p rule */
Avoid !important in application styles. It makes debugging extremely difficult and creates a specificity arms race. Reserve it for:
- User stylesheets (accessibility overrides)
- Third-party CSS you cannot modify
- Utility classes that must always apply (e.g.,
.hidden { display: none !important; })
Practical Advice
Keep specificity low
The lower the specificity of your base styles, the easier it is to override them logically:
/* Low specificity — easy to override */
.button {
background: blue;
}
/* High specificity — harder to override */
div.container > form button.btn {
background: blue;
}
Use BEM or utility classes to avoid specificity issues
BEM (Block-Element-Modifier) methodology uses only single-class selectors:
.button {
}
.button--primary {
}
.button__icon {
}
All rules have specificity (0, 1, 0), so later rules always win — predictable and easy to reason about.
Avoid ID selectors in CSS
One ID selector beats any number of class selectors. IDs should be used for JavaScript hooks, not styling.
The cascade is your friend
When specificity is equal, source order wins. This is not a bug — it is a feature. Use it to define defaults first and overrides later, rather than escalating specificity.
Inspecting Specificity in DevTools
Browser DevTools show applied and overridden CSS rules with strikethrough for losing declarations. Hovering over a selector in the Rules panel shows its computed specificity value. This is the fastest way to debug unexpected style behaviour.