CSS Specificity — How It Actually Works
Finally understand CSS specificity. Learn the specificity hierarchy, how to calculate scores, when to use !important, and practical debugging tips.
CSS Specificity — How It Actually Works
"Why isn't my CSS working?" — If you've ever asked this, the answer is almost always specificity. Understanding specificity means you'll spend less time fighting CSS and more time building.
What is Specificity?
Specificity is how the browser decides which CSS rule wins when multiple rules target the same element. It's a scoring system.
Think of it like a tiebreaker. When two rules conflict:
.card { color: blue; }
div.card { color: red; }
The element is red. div.card has higher specificity than .card.
The Specificity Hierarchy
From lowest to highest priority:
| Level | Selector Type | Example |
|---|---|---|
| 0 | Universal, combinators | *, >, +, ~ |
| 1 | Element, pseudo-elements | div, p, ::before |
| 2 | Class, attribute, pseudo-class | .card, [type="text"], :hover |
| 3 | ID | #header |
| ∞ | Inline styles | style="color: red" |
| ∞+ | !important | color: red !important |
Calculating Specificity
Think of specificity as a three-digit score: (IDs, Classes, Elements)
/* (0, 0, 1) — one element */
p { }
/* (0, 1, 0) — one class */
.card { }
/* (0, 1, 1) — one class + one element */
div.card { }
/* (1, 0, 0) — one ID */
#header { }
/* (1, 1, 1) — one ID + one class + one element */
#header .nav a { }
/* (0, 2, 1) — two classes + one element */
.sidebar .nav-link:hover { }
Comparison Rules
- Compare IDs first. Higher ID count wins.
- If IDs are tied, compare classes.
- If classes are tied, compare elements.
#header .nav { } /* (1, 1, 0) ← wins */
.header .nav .link { } /* (0, 3, 0) — 3 classes loses to 1 ID */
IDs always beat classes, no matter how many classes you stack.
Common Specificity Battles
Overriding Framework Styles
When Bootstrap or a UI library sets styles, they often use specific selectors:
/* Bootstrap uses: */
.btn.btn-primary { color: #fff; } /* (0, 2, 0) */
/* Your override needs at least: */
.my-button.btn.btn-primary { color: #000; } /* (0, 3, 0) */
Utility Class Losing
.text-red { color: red; } /* (0, 1, 0) */
.card .title { color: black; } /* (0, 2, 0) ← wins */
Your utility class loses because the other rule has higher specificity.
Rules to Live By
1. Avoid IDs for Styling
IDs have nuclear-level specificity. Once you use #header for styling, you need another ID to override it. Use classes instead.
2. Avoid !important
!important breaks the cascade. It's a sledgehammer. The only way to override !important is with another !important that has equal or higher specificity. This creates an arms race.
When !important is okay:
- Overriding third-party library styles you can't modify
- Utility classes that absolutely must apply (e.g.,
.hidden { display: none !important })
3. Keep Specificity Flat
Better:
.card-title { font-size: 1.25rem; }
Worse:
.sidebar .card .card-header .card-title { font-size: 1.25rem; }
The flatter your selectors, the easier they are to override and maintain.
4. Use the "Source Order" Tiebreaker
When specificity is equal, the last rule wins:
.text { color: blue; }
.text { color: red; } /* ← wins (same specificity, comes later) */
This is why putting your custom CSS after a framework's CSS works.
Debugging Specificity
Browser DevTools
Open DevTools → select an element → look at the Styles panel. Crossed-out properties are being overridden by higher-specificity rules. DevTools shows you exactly which rule is winning and why.
The "Add a Class" Fix
If your style isn't applying, add another class to increase specificity:
/* Not working: */
.title { color: blue; }
/* Fix — make it more specific: */
.card .title { color: blue; }
Specificity and CSS Layers
CSS Cascade Layers (@layer) are a modern way to manage specificity at scale:
@layer base, components, utilities;
@layer base {
a { color: blue; }
}
@layer utilities {
.text-red { color: red; } /* wins even with lower specificity */
}
Layers let you control which group of styles takes priority, regardless of specificity within each layer.
Key Takeaways
- Specificity score: count (IDs, Classes, Elements) — IDs always win over classes
- Avoid IDs and
!importantfor styling — they cause specificity wars - Keep selectors flat —
.card-titlebeats.sidebar .card .header .titlein maintainability - When specificity is equal, last rule in source order wins
- Use DevTools to debug which rule is winning
🚀 Practice What You Learned
Apply these concepts with hands-on coding challenges: