C.W.K.
Stream
Lesson 02 of 05 · published

Specificity: The Numeric Score That Decides Ties

~12 min · specificity, cascade, important

Level 0Markup Novice
0 XP0/34 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"!important is the answer when you don't understand specificity. The fix is to understand specificity."

What Is Specificity?

Specificity is a numeric score CSS computes for each selector. When several rules target the same element with conflicting declarations, the selector with the highest specificity wins. If specificities tie, the rule that comes later in source order wins.

That's the whole algorithm. The hard part is calculating it correctly.

The Four-Column Score: (a, b, c, d)

Specificity is a tuple of four counts, compared left-to-right:

  • a — inline styles: 1 if the declaration is in a style="..." attribute, else 0.
  • b — IDs: count of #id selectors.
  • c — classes, attributes, pseudo-classes: count of .class, [attr], :hover, :nth-child, etc.
  • d — elements and pseudo-elements: count of div, ::before, etc.

Compare tuples left-to-right: any difference in a decides; if a ties, look at b; and so on. This isn't decimal arithmetic. 10 classes (0,0,10,0) does NOT beat 1 ID (0,1,0,0) — IDs are a higher column.

Examples, Computed By Hand

  • p → (0,0,0,1)
  • p.lead → (0,0,1,1)
  • article p.lead → (0,0,1,2)
  • article p.lead.featured → (0,0,2,2)
  • #main p → (0,1,0,1)
  • nav a:hover → (0,0,1,2)
  • a[href^="https"] → (0,0,1,1)
  • style="color: red" → (1,0,0,0)
  • :where(.btn, .button) → (0,0,0,0) — :where always contributes 0
  • :is(.btn, #cta) → (0,1,0,0) — :is takes the highest argument's specificity
  • :not(.btn, #cta) → (0,1,0,0) — same rule as :is

The !important Override

Adding !important to a declaration moves it to a higher tier of the cascade — it beats every non-!important declaration regardless of specificity. Two !important declarations compete on specificity again.

!important is almost always the wrong answer. It signals "I couldn't figure out why my rule wasn't winning, so I escalated." The right answer is usually one of:

  • Increase your selector's specificity to match the competition.
  • Refactor the conflicting rule to be less specific.
  • Use a cascade layer (Track 7's lesson) to control ordering explicitly.

The legitimate uses of !important are narrow: user stylesheets that must override author styles for accessibility, utility classes intended to always win (Tailwind's plugin overrides do this), and the very occasional third-party library override.

The :where() Magic For Design Systems

:where() matches the same selectors as :is() but always reports specificity (0,0,0,0). This is exactly what a design system needs: base styles that any author rule can override with a single class:

Specificity isn't power, it's commitment. Every time you raise specificity, you make the rule harder to override. The lowest-specificity rule that does the job is the most flexible. Reach for higher specificity only when you've measured that lower didn't work.

Reading Specificity In DevTools

Chrome DevTools shows specificity next to each rule in the Styles panel: hover the selector text and a tooltip displays the (a,b,c,d) score. Firefox shows it similarly. This is the fastest way to debug: "why is the blue style winning? Oh, that other rule has an ID I didn't notice."

The Cascade Order, In Full

When two rules apply, the browser walks this ordered list and the first decisive comparison wins:

  1. Origin and importance — user-important > author-important > author > user > UA-default (and UA-important is below all user).
  2. Cascade layer — later-declared layers beat earlier ones (Track 7).
  3. Specificity — the score we just computed.
  4. Source order — the rule written later wins.

Pippa's Note

Pippa's WebUI uses Tailwind, which means every utility class is (0,0,1,0). Conflicts almost always reduce to source order — and Tailwind's build emits classes in a deterministic order. The cwk-site essays, by contrast, use small custom CSS modules where specificity matters; for those, the codebase uses :where() for base styles and avoids IDs entirely. Different stacks, same lesson: pick a specificity strategy and stick to it.

Code

Step-by-step specificity·css
/* Calculating specificity in your head */

/* (0,0,0,1) — type only */
h1 { color: black; }

/* (0,0,1,1) — type + class */
h1.title { color: navy; }

/* (0,0,1,2) — two types + class */
article h1.title { color: forest; }

/* (0,1,0,1) — id + type, beats anything without an id */
#main h1 { color: maroon; }

/* (1,0,0,0) — inline style, beats almost everything */
/* <h1 style="color: tomato;"> */

/* RESULT: with all five rules active, the inline style wins.
   Remove inline → #main h1 wins.
   Remove #main → article h1.title wins.
   Remove article h1.title → h1.title wins.
   Remove h1.title → h1 wins. */
:where() for design system bases·css
/* Common :is() vs :where() difference */

/* Specificity (0,1,0,1) — the id contributes */
:is(article, section, #featured) h1 { font-weight: 700; }

/* Specificity (0,0,0,1) — zero from :where, just the h1 */
:where(article, section, #featured) h1 { font-weight: 700; }

/* Design system base — make it trivially overrideable */
:where(.btn) {
  padding: 0.5rem 1rem;
  border-radius: 4px;
  background: #5BA3D8;
  color: white;
}

/* App author overrides with (0,0,1,0) — no specificity war needed */
.primary { background: #FF7A52; }
!important escalation vs cascade layers·css
/* The !important escalation pattern (avoid when possible) */

/* Library style */
.toast { background: red !important; }

/* App override attempt — fails because library has !important */
.toast.success { background: green; }

/* App escalation — works but ugly */
.toast.success { background: green !important; }

/* Better fix: use cascade layers (Track 7) */
@layer library, app;
@layer library { .toast { background: red; } }
@layer app { .toast.success { background: green; } }
/* App layer wins regardless of specificity — no !important needed */

External links

Exercise

Take three CSS rules from any production stylesheet (open DevTools → Sources → any .css file). For each, calculate the (a,b,c,d) score by hand. Verify in DevTools by hovering the selector. Then pick a rule that ISN'T applying when you expected it to. Walk the cascade: which rule with higher specificity is overriding it? Is the right fix to raise yours, lower theirs, or refactor with :where()?
Hint
If the override is from a third-party library, you usually can't refactor theirs. Cascade layers (next to Track 7) are the principled solution; raising your specificity to match is the firefighting solution.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.