—CSS Series · Post 10: Specificity & The Cascade

Specificity & The Cascade — CodeHerWay
CSS Series · Post 10

Specificity &
The Cascade

CSS has a built-in rulebook that decides which style wins when two rules conflict. Once you understand it, the mystery of "why isn't my style applying?" disappears entirely.

CSS Beginner Series: Post 10 ~11 min read

💬 "I Wrote the Rule — Why Isn't It Working?"

You add a style. It does nothing. You check the spelling. Fine. You check the selector. Correct. You add !important and it works but now something else breaks. You spend 20 minutes wondering if CSS is just broken.

It's not broken. CSS has a priority system called the cascade, and there are two rules competing to style the same element — one is winning and the other is losing silently. Understanding which rule wins, and why, is one of the most practically useful things you'll learn.

🧠 The Core Concept

When multiple CSS rules target the same element and the same property, only one value wins. CSS decides the winner using a scoring system called specificity, with a tiebreaker based on order. Knowing the scores ends the mystery.

🧱 The Cascade — How CSS Decides

The word "Cascading" in CSS is the cascade. Rules flow down in priority order. CSS evaluates three things, from most to least important:

1
Specificity — how targeted is the selector? More specific = higher priority.
2
Order — if specificity ties, the rule that appears later in the file wins.
3
Origin — author styles (your CSS) override browser default styles.

🧱 Specificity — The Scoring System

Every CSS selector gets a score. Higher score wins. The score has three slots: [IDs] — [classes/attributes/pseudo-classes] — [elements/pseudo-elements]. Count each type in your selector and that's your score.

Inline styles style="color: pink" 1-0-0-0 — beats everything
ID selectors #hero { } 0-1-0-0
Class / attribute / pseudo-class .card { } [type="text"] { } :hover { } 0-0-1-0
Element / pseudo-element p { } h1 { } ::before { } 0-0-0-1

Calculating the Score

Count each type of selector and assign its slot value. The higher total score wins. When two selectors have the same score, the one written later in the file wins.

p { }
0-0-0-1 — 1 element
.card { }
0-0-1-0 — 1 class
.card p { }
0-0-1-1 — 1 class + 1 element
.card .title { }
0-0-2-0 — 2 classes
#hero .card p { }
0-1-1-1 — 1 ID + 1 class + 1 element
style="color: pink"
1-0-0-0 — inline beats all selectors
styles.css
CSS · Specificity in Practice
/* Score: 0-0-0-1 — element selector */ p { color: gray; } /* Score: 0-0-1-0 — class selector. WINS over p { } */ .intro { color: white; } /* Score: 0-0-1-1 — class + element. WINS over .intro { } */ .card p { color: hotpink; } /* If the HTML is: <div class="card"><p class="intro">Hi</p></div> Both .intro and .card p match. .card p wins (0-0-1-1 beats 0-0-1-0) — text is hotpink. */

Order as a Tiebreaker

When specificity is equal, the rule that appears later in the stylesheet wins. This is why the order of your CSS rules matters — and why you should write more specific overrides after your base styles.

styles.css
CSS · Order Matters
/* Same specificity (0-0-1-0) — ORDER decides the winner */ .button { background: #b44aff; /* purple */ } .button { background: #ff69b4; /* pink — THIS WINS. It comes last. */ } /* Classic pattern: base styles first, overrides after */ .card { background: #12121a; } .card-featured { background: #1a1a2e; } /* same score, comes later → wins */

Inheritance — Properties Passed Down

Some CSS properties automatically pass their value to child elements. color, font-family, font-size, line-height, and letter-spacing are inherited. margin, padding, border, and background are not.

styles.css
CSS · Inheritance
/* Set font on body — ALL children inherit it automatically */ body { font-family: 'Space Grotesk', sans-serif; color: #e0e0e8; /* Every p, h1, span, div etc inherits these values */ } /* Override only what you need to change */ .code-label { font-family: 'Fira Code', monospace; /* overrides inherited */ } /* Force inheritance on a non-inheriting property */ .match-parent { border: inherit; /* forces border to inherit from parent */ background: inherit; }

!important — The Nuclear Option

!important overrides all specificity and order. It seems like the answer to every stubborn style problem — and it is, in the short term. In the long term, it creates a specificity arms race where you end up adding !important to override your other !important rules, and your stylesheet becomes unmaintainable.

⚠️ !important — Use It Rarely

Legitimate uses: utility classes that must always apply (.hidden { display: none !important; }), overriding third-party CSS you can't change, and temporary debugging. In your own CSS, if you're reaching for !important, the real fix is usually to refactor your selectors so the right one has higher specificity naturally.

🛠

Mini Build: Break It and Fix It

The best way to understand specificity is to deliberately cause conflicts and then resolve them correctly — without reaching for !important.

01

Set up conflicting rules. Write CSS that creates a specificity conflict and predict which color wins before opening the browser.

styles.css
CSS · Conflict Setup
/* Before opening the browser — predict what color the paragraph will be */ p { color: gray; } /* 0-0-0-1 */ .section p { color: white; } /* 0-0-1-1 */ .intro { color: #ff69b4; } /* 0-0-1-0 */ /* HTML: <section class="section"><p class="intro">Text</p></section> Which rule wins? .section p → 0-0-1-1 .intro → 0-0-1-0 Answer: white (from .section p — it scores higher) */
02

Fix it without !important. To make .intro win, raise its specificity instead of using !important.

styles.css
CSS · Fix Without !important
/* Option 1: add a class to beat .section p */ .section .intro { color: #ff69b4; } /* 0-0-2-0 — wins */ /* Option 2: reorder so .intro comes after .section p */ .section p { color: white; } .intro { color: #ff69b4; } /* same score, but LATER → wins */ /* Option 3: avoid the conflict entirely — use BEM naming */ .section__intro { color: #ff69b4; } /* one class, no conflicts */
03

Practice in DevTools. Open any page, inspect an element, and look at the Styles panel. Rules that are crossed out lost the specificity battle — the winning value is at the top. This is the clearest way to see the cascade in action.

💛 You Can Debug Your Own CSS Now

The cascade isn't a mystery system — it's a scoring algorithm. Once you know the scores, you can look at any set of conflicting rules and predict exactly which one wins. That's a debugging superpower.

  • The cascade prioritizes: specificity first, then order, then origin
  • Inline styles (1-0-0-0) beat IDs (0-1-0-0) beat classes (0-0-1-0) beat elements (0-0-0-1)
  • Count the selector types to calculate the score — higher wins
  • Equal scores? The rule written later in the file wins
  • Inherited properties flow from parent to child automatically
  • !important wins everything — but creates problems. Fix specificity instead.
  • Crossed-out styles in DevTools = lost the specificity battle

Next: Transitions, Transforms & CSS Variables — motion, interactivity, and building a reusable design token system.

🧾 DevFession

My first real project had seventeen !important declarations. Seventeen. By the end I was writing color: pink !important to override a color: pink !important I'd written earlier in the same file. The stylesheet had become a war against itself.

The day I learned specificity scoring and refactored that project was genuinely satisfying. Every !important came out. The file shrank. The styles all worked. The problem wasn't that CSS was frustrating — the problem was that I'd been brute-forcing past a system I didn't understand.

Learn the scoring. Open DevTools. Look at the crossed-out styles. Follow the trail. You'll fix in five minutes what used to take an hour.

Previous
Previous

—CSS Series · Post 11: Transitions, Transforms & CSS Variables

Next
Next

—CSS Series · Post 07: Flexbox