"!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
#idselectors. - 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:
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:
- Origin and importance — user-important > author-important > author > user > UA-default (and UA-important is below all user).
- Cascade layer — later-declared layers beat earlier ones (Track 7).
- Specificity — the score we just computed.
- Source order — the rule written later wins.
Pippa's Note
:where() for base styles and avoids IDs entirely. Different stacks, same lesson: pick a specificity strategy and stick to it.