Specificity and the Cascade
The cascade is the algorithm CSS uses to resolve conflicts when multiple rules target the same element. Understanding it deeply is the key to writing CSS that works predictably without resorting to !important.
1. The Four Cascade Layers (Priority Order)
When two CSS rules conflict, the browser resolves by checking these in order:
- Origin and Importance (Author > User > Browser default;
!importantreverses). @layerorder (Lower layers lose to higher layers regardless of specificity).- Specificity (Higher specificity wins).
- Source Order (Later rules win if specificity is equal).
2. Calculating Specificity
Specificity is a score with four buckets: (A, B, C, D) where A is highest:
| Selector | A | B | C | D | Score |
|---|---|---|---|---|---|
* (universal) | 0 | 0 | 0 | 0 | 0 |
li (element) | 0 | 0 | 0 | 1 | 1 |
li::before | 0 | 0 | 0 | 2 | 2 (pseudo-element = 1) |
.nav (class) | 0 | 0 | 1 | 0 | 10 |
[type="text"] (attribute) | 0 | 0 | 1 | 0 | 10 |
.nav li | 0 | 0 | 1 | 1 | 11 |
#main (ID) | 0 | 1 | 0 | 0 | 100 |
Style attribute style="" | 1 | 0 | 0 | 0 | 1000 |
!important | ∞ | — | — | — | Infinite |
Worked Examples
h1 { color: red; } /* (0, 0, 0, 1) = 1 */
.title { color: blue; } /* (0, 0, 1, 0) = 10 */
#header h1 { color: green; } /* (0, 1, 0, 1) = 101 */
/* Result: h1 inside #header is GREEN */
3. Common Specificity Mistakes
Mistake 1: ID Selectors for Styling
/* ❌ Creates high specificity — hard to override later */
#hero-button { background: red; }
/* ✅ Use classes instead */
.hero-button { background: red; }
Mistake 2: Over-Qualifying Selectors
/* ❌ Unnecessarily high specificity: div.card = 0,0,1,1 */
div.card { padding: 1rem; }
/* ✅ Just the class: .card = 0,0,1,0 */
.card { padding: 1rem; }
Mistake 3: Chain-Qualifying for WordPress
Sometimes in WordPress you have to fight theme styles. Correct escalation:
/* Theme uses: .site-header .nav a (0,0,2,1) = 21 */
/* Step 1: Try matching specificity */
.site-header .main-nav a { color: var(--accent); } /* (0,0,2,1) — wins by order */
/* Step 2: Outspecify */
body .site-header .nav a { color: var(--accent); } /* (0,0,2,2) — wins */
/* Step 3 (last resort) */
.site-header .nav a { color: var(--accent) !important; }
4. The !important Keyword
!important overrides all specificity rules for that property:
.element { color: red !important; }
/* Even an inline style="color: blue" can be beaten by !important in a stylesheet */
When !important is legitimate:
- WordPress/WooCommerce inline styles from Customizer output.
- Utility classes that must always apply (
.hidden,.sr-only). - Accessibility overrides.
When to avoid it:
- General component styling.
- When you should just increase specificity.
- When it's used to fight your own CSS (a sign of poor architecture).
5. The :is() and :where() Specificity Trick
/* :is() uses the specificity of its HIGHEST-specificity argument */
:is(#main, .card, p) a { color: red; }
/* Specificity = (0,1,0,1) because #main is included */
/* :where() ALWAYS has zero specificity */
:where(h1, h2, h3, h4) { margin-bottom: 0.5em; }
/* Specificity = (0,0,0,0) — easy to override anywhere */
:where() is perfect for CSS resets — you want the styles to be easily overridden:
/* Base reset with :where() — zero specificity, always overridable */
:where(p, ul, ol, h1, h2, h3, h4, h5, h6, blockquote) {
margin: 0;
padding: 0;
}
6. @layer for Cascade Control
@layer lets you define custom cascade layers that sit below unlayered styles, regardless of specificity:
/* Any rule outside layers wins over all layers */
@layer reset, base, components, utilities;
@layer reset {
/* Even with low specificity, this loses to any unlayered CSS */
h1 { font-size: 2rem !important; } /* !important in a low-priority layer is still overridden by unlayered */
}
/* Unlayered CSS — always wins */
h1 { font-size: 3rem; }
7. Quick Specificity Decision Tree
Does the rule NOT apply?
→ Is another rule crossing it out in DevTools (strikethrough)?
YES → Specificity conflict
→ Is the winning rule using an ID? → Replace your ID with a class.
→ Is it inline style? → Use !important sparingly, or add your CSS inline.
→ Is it from a plugin/theme? → Match or beat specificity, or use !important.
NO → CSS not loading
→ Check enqueue order, file path, caching.