All articles
CSS Fundamentals

CSS Specificity Explained Once and For All

16 December 2024 7 min read CSS Fundamentals

Stop fighting the cascade. Understand the specificity scoring system, the stacking order, how !important fits in, and practical rules for styles that don't fight each other.

CSS specificity is responsible for more developer frustration than almost any other CSS concept. The styles aren't applying — why? The answer, almost always, is specificity. Let's untangle it once and for all.

How specificity is calculated

Every selector gets a score based on what it contains. Think of it as three separate digits: (A, B, C).

  • A — ID selectors (#nav). Each one adds 1 to A.
  • B — Class selectors (.card), attribute selectors ([type="text"]), pseudo-classes (:hover, :focus). Each adds 1 to B.
  • C — Element selectors (div, p, a), pseudo-elements (::before). Each adds 1 to C.
Selector A B C Score
p001(0,0,1)
.card010(0,1,0)
.card p011(0,1,1)
#nav .link110(1,1,0)
style=""Inline(1,0,0,0)

Compare from left to right. The first digit that differs wins. (1,0,0) beats (0,5,0). A single ID beats any number of classes.

The !important nuclear option

!important overrides all specificity. It's a specificity escape hatch — not a solution.

/* Bad — using !important to fight a specificity battle */
.button { color: red !important; }

/* Good — fixing the root cause: lower the conflicting selector's specificity */

If you're reaching for !important more than once a month, your CSS architecture has a specificity problem. The fix is flatter selectors and a consistent naming convention like BEM.

Practical rules for avoiding specificity wars
  1. Never use IDs in CSS. Use classes. IDs score (1,0,0) and cause specificity debt that's painful to undo.
  2. Keep selectors shallow. One or two levels deep at most. .card .title is fine. .page .section .card .title span is a problem.
  3. Use BEM or a similar convention. .card__title is a single-class selector — easy to override, impossible to conflict.
  4. Use :where() for zero-specificity resets. :where(h1, h2, h3) { margin: 0; } has a specificity of (0,0,0) — any class can override it.