CSS Cascade & Specificity

โ–ถ Try It Yourself

Have you ever written a CSS rule and wondered why it had no effect? The answer almost always lies in the cascade โ€” the algorithm browsers use to decide which rule wins when multiple rules target the same element and property. Understanding the cascade, specificity, and inheritance turns CSS debugging from guesswork into a systematic process. In this lesson you will learn all three systems and how to write CSS that stays predictable.

The Three Pillars: Cascade, Specificity, Inheritance

1 โ€” The Cascade

When two rules conflict, the browser resolves it in order:

  1. Origin and importance โ€” browser default < author stylesheet; !important flips this
  2. Specificity โ€” more specific selector wins
  3. Source order โ€” equal specificity: later rule wins
  4. Inheritance โ€” no rule applies: inheritable properties flow from parent

2 โ€” Specificity Scoring

Selector Type Score Example
Inline style="" (1,0,0,0) <p style="color:red">
ID selector (0,1,0,0) #header
Class / attribute / pseudo-class (0,0,1,0) .btn, [type], :hover
Element / pseudo-element (0,0,0,1) p, ::before
Universal, combinators (0,0,0,0) *, >, +

3 โ€” Inheritance

Properties That Inherit Properties That Do NOT Override With
color, font-size, font-family margin, padding, border inherit keyword
line-height, letter-spacing background, width, height initial to reset
text-align, visibility display, position, overflow unset for either
Note: Specificity is a tuple, not a single number. A score of (0,0,1,0) always beats (0,0,0,99) because one class beats any number of element selectors. Think of it as four separate columns โ€” the leftmost difference decides the winner.
Tip: Keep selectors as simple as possible โ€” aim for single-class selectors. Low specificity is easy to override; high specificity leads to escalating specificity wars that make CSS unpredictable.
Warning: Every !important you add requires another !important to override later โ€” creating a specificity arms race. Reserve it strictly for utility classes or overriding unavoidable third-party inline styles.

Basic Example

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Cascade & Specificity</title>
  <style>
    /* (0,0,0,1) โ€” element */
    p { color: #64748b; font-family: system-ui, sans-serif; }

    /* (0,0,1,0) โ€” class beats element */
    .highlight { color: #1d4ed8; }

    /* (0,0,1,1) โ€” class + element */
    section .highlight { color: #16a34a; }

    /* (0,1,0,0) โ€” ID beats all classes */
    #special { color: #dc2626; }

    /* Source order: later wins on equal specificity */
    .box { background: lightblue; }
    .box { background: lightyellow; }

    /* Inheritance */
    .parent-teal { color: teal; font-weight: bold; }
  </style>
</head>
<body>
  <p>Grey โ€” element (0,0,0,1).</p>
  <p class="highlight">Blue โ€” class (0,0,1,0) beats element.</p>
  <section>
    <p class="highlight">Green โ€” section .highlight (0,0,1,1) beats .highlight.</p>
  </section>
  <p id="special" class="highlight">Red โ€” ID (0,1,0,0) beats class.</p>
  <div class="box">Yellow โ€” source order decides equal specificity.</div>
  <div class="parent-teal">
    <span>Inherits teal color and bold weight.</span>
  </div>
</body>
</html>

How It Works

Step 1 โ€” Element Selector Is the Baseline

p { color: #64748b; } scores (0,0,0,1) โ€” the weakest rule, easily overridden. It is the foundation for all paragraphs not targeted by anything more specific.

Step 2 โ€” Class Beats Element

.highlight scores (0,0,1,0). The class column beats the element column โ€” any paragraph with class=”highlight” turns blue regardless of source order.

Step 3 โ€” Compound Selector Adds Specificity

section .highlight scores (0,0,1,1) โ€” one class plus one element. The element column is now 1 vs. 0, so it beats .highlight alone.

Step 4 โ€” ID Wins Against Any Class

#special scores (0,1,0,0). The ID column immediately wins over any number of classes.

Step 5 โ€” Source Order Breaks Specificity Ties

Both .box rules share specificity (0,0,1,0). The tiebreaker is source order: the second declaration wins.

Real-World Example: Overriding Third-Party Styles

/* Library code you cannot edit (specificity 0,1,1,0): */
/* #app .ui-button { background: #e2e8f0; color: #1e293b; } */

/* โ”€โ”€ Approach 1: Match specificity + place after โ”€โ”€ */
#app .my-button {
  background: #4f46e5;
  color: white;
  border-radius: 6px;
  cursor: pointer;
}

/* โ”€โ”€ Approach 2: Add one extra class for higher specificity โ”€โ”€ */
.theme-custom .my-button {
  background: #4f46e5;
  color: white;
}

/* โ”€โ”€ Approach 3: Zero-specificity base styles with :where() โ”€โ”€ */
:where(button, .btn-reset) {
  appearance: none;
  background: transparent;
  border: none;
  padding: 0;
  cursor: pointer;
  font: inherit;
}

/* โ”€โ”€ currentColor keeps border in sync with text โ”€โ”€ */
.themed-icon {
  color: #4f46e5;
  border: 2px solid currentColor;
}
.themed-icon svg {
  fill: currentColor;
  width: 1em;
  height: 1em;
}

Common Mistakes

Mistake 1 โ€” Using !important to fight specificity

โŒ Wrong โ€” leads to escalating arms race:

.btn          { color: blue !important; }
.hero .btn    { color: white !important; }

โœ… Correct โ€” raise specificity deliberately:

.btn          { color: blue; }
.hero .btn    { color: white; }

Mistake 2 โ€” Assuming all properties inherit

โŒ Wrong โ€” border does not inherit:

.parent { border: 2px solid red; }
/* children do NOT automatically get a border */

โœ… Correct โ€” use the inherit keyword explicitly:

.child { border: inherit; }

Mistake 3 โ€” Thinking source order overrides specificity

โŒ Wrong โ€” a class after an ID still loses:

#nav { color: blue; }  /* (0,1,0,0) */
.nav { color: red; }   /* (0,0,1,0) โ€” comes later but still loses */

โœ… Correct โ€” source order only breaks ties of equal specificity:

#nav.nav { color: red; } /* (0,1,1,0) โ€” now wins over #nav */

▶ Try It Yourself

Quick Reference

Concept Key Rule Example
Inline style (1,0,0,0) โ€” highest authored specificity style="color:red"
ID selector (0,1,0,0) #header
Class selector (0,0,1,0) .btn
Element selector (0,0,0,1) p, div
Source order Tiebreaker when specificity is equal Later rule wins
!important Overrides all normal rules; avoid abuse color: red !important;
Inheritance Text props inherit; box/layout do not color inherits; margin does not

🧠 Test Yourself

Two rules target the same element: #nav .link vs .site-nav .menu .link. Which wins?





โ–ถ Try It Yourself