CSS Specificity Deep Dive
Count IDs, classes, and elements correctly — :is/:where/:not, inline styles, !important, and how layers change who actually wins.
- css
- specificity
- cascade
Specificity is the cascade’s tie-breaker when origin, importance, and layer are equal. Miscounting it is why “I added a class and nothing happened” is a daily stand-up topic. Master the scoreboard, then design systems that rarely need to look at it.
Docs: MDN Specificity, Cascade.
The scoreboard
Think of a tuple (IDs, classes/attributes/pseudo-classes, elements/pseudo-elements). Inline styles sit above that tuple (except vs !important games).
| Selector | IDs | Classes | Elements |
|---|---|---|---|
* |
0 | 0 | 0 |
div |
0 | 0 | 1 |
.card |
0 | 1 | 0 |
.card.featured |
0 | 2 | 0 |
#nav |
1 | 0 | 0 |
a:hover |
0 | 1 | 1 |
input[type="text"] |
0 | 1 | 1 |
div.card > p::first-line |
0 | 1 | 2 |
.nav a {
color: blue;
} /* 0,1,1 */
a.nav-link {
color: red;
} /* 0,1,1 — later wins if equal */
#nav a {
color: green;
} /* 1,0,1 — beats both */
There is no carry: 11 classes do not equal 1 ID.
Pseudo-classes that rewrite the count
:where() — zero specificity
:where(.token, .chip, .tag) {
border-radius: 999px;
}
.chip {
background: var(--brand); /* easily overrides */
}
Ideal for base component defaults you want to override with one class.
:is() / :not() — specific argument wins
:is(.card, #hero) .title {
/* specificity includes #hero when that branch matches */
font-weight: 700;
}
A “harmless” :is() that includes an ID can make the whole selector ID-level. :not(.a, .b) uses the most specific argument inside.
:has()
Counts like a pseudo-class, plus the specificity of its relative selector argument (similar spirit to :is()).
.card:has(img) {
/* class + contents of :has() */
padding: 0;
}
Inline and !important
<p class="note" style="color: red">…</p>
.note {
color: blue !important; /* important author vs inline — importance layer rules apply */
}
!important escalates importance, which is resolved before normal specificity comparisons within the appropriate origin/layer. Teams that spray !important lose the ability to reason locally.
Layers beat specificity across buckets
@layer components, utilities;
@layer components {
#widget .button {
background: black;
}
}
@layer utilities {
.bg-brand {
background: var(--brand);
}
}
Utility wins despite lower specificity because later layer wins for normal declarations. Learn layers with specificity — not as a replacement for counting, but as a way to stop counting for overrides.
Debugging method
- DevTools → computed → find property → which rule won?
- Struck-through rules: note specificity badges / layer labels.
- Check inline styles and
!important. - Check shadow DOM / user-agent / extension styles if truly impossible.
Interview out-loud
“Specificity compares IDs, then classes/attributes/pseudo-classes, then elements/pseudo-elements. Inline styles beat those; !important changes the importance tier. :where() is zero, :is() takes the highest argument. Equal specificity falls to source order. Cascade layers can make a weaker selector win across layers. I prefer flat classes and layers over ID wars.”
Footguns
- IDs in app CSS.
- Nested Sass producing accidental super-specific selectors.
:is(#a, .b)poisoning specificity.- Fighting a layer with specificity instead of fixing layer order.
- Element selectors in deep chains (
nav ul li a span) for cosmetics.
Related
Further reading
Related guides
- Cascade & SpecificityHow CSS decides the winner — origin, importance, layers, specificity, and order — in human terms.
- Accent Color and Form ControlsUse accent-color to theme native checkboxes, radios, ranges, and progress — what it paints, what it skips, and when to custom-style instead.
- Auto-fit vs Auto-fillauto-fit collapses empty tracks; auto-fill keeps them. Use both with minmax for responsive card grids without media-query soup.
- BEM Naming MethodologyBlock, Element, Modifier naming for CSS — how it limits specificity wars, what to name, and when utility or CSS modules beat strict BEM.
- blend-mode Basicsmix-blend-mode vs background-blend-mode — how pixels combine, isolation, and the stacking/readability traps in product UI.