CSS fundamentals · 3 / 8
lesson 3

Specificity and the cascade

Why one rule beats another — and why !important is almost never the right fix.

~ 18 min read·lesson 3 of 8
0 / 8

You write p { color: navy } at the top of your stylesheet and it works. Then you add .intro { color: red } further down, expecting your intro paragraph to come out red. It does. So far so good. Then a teammate adds body p { color: green } somewhere in the middle of the file — and suddenly your intro paragraph is green, even though .intro is "more specific" in your head. Welcome to the cascade.

The cascade is the set of rules the browser uses to pick a winner when two CSS rules try to set the same property to different values on the same element. It is not random and not first-come-first-served — it is a small ranked list, and once you know the list, every "why is this colour wrong?" mystery becomes a quick check.

When two rules disagree

Conflict only kicks in when two rules touch the same property. Two rules that set different properties just stack — that is what lesson 1 covered. So this whole lesson only matters when you have something like:

styles.css
p { color: navy; }
.intro { color: red; }

Both rules match <p class="intro">. Both want to set color. Only one can win. The browser picks a winner using three checks, in this order:

  1. Origin — where the rule comes from (your stylesheet vs. the browser's defaults vs. user styles). Your stylesheet beats the browser default.
  2. Specificity — how "specific" the selector is. Higher specificity wins.
  3. Source order — if everything else is tied, the rule that comes later in the file wins.

For day-to-day work the first check almost never matters — you are always overriding the browser default — so the real story is specificity, then source order as the tiebreaker.

Specificity, the score that decides

Specificity is a score the browser calculates for each selector. It is written as three numbers: (ids, classes, types). Read them like a tennis score — left-most number wins outright, ties go to the next number to the right.

  • Ids — count every #id in the selector. Each one adds one to the first number.
  • Classes / attributes / pseudo-classes — count every .class, [attr], and :hover-style pseudo. Each one adds one to the second number.
  • Types / pseudo-elements — count every tag name and every ::before-style pseudo-element. Each one adds one to the third number.

Some examples to anchor the rule:

specificity.css
a              /* (0,0,1) */
.intro         /* (0,1,0) */
nav a          /* (0,0,2) */
nav a.active   /* (0,1,2) */
#header a      /* (1,0,1) */

Compare two rules on the same element by lining up their scores. The first number that differs decides the winner. So .intro beats a because (0,1,0) is greater than (0,0,1) — the second number is higher and the first is tied. And #header a beats nav a.active because (1,0,1) has an id and (0,1,2) does not. The number of types (2 vs 1) does not matter once a higher slot is unequal.

selector id class type .intro010 body p002 .intro wins — class slot is 1 vs 0, types don't enter the comparison.
Specificity is three slots. Compare left to right and the first difference decides.

The earlier mystery now has a tidy answer. .intro is (0,1,0). body p is (0,0,2). The class slot is 1 vs 0, so .intro wins on the second number — and the green rule on the screen had to be coming from somewhere with at least one class. Most "specificity surprises" turn out to be a class you didn't know was matching.

check your understanding
Which selector has the highest specificity?

Source order, the tiebreaker

When two rules tie on specificity, the rule that comes later in the source wins. Source order means the order the browser sees the rules — which is the order you wrote them in a single file, or the order the files load if you split your styles. The last word wins.

styles.css
.btn { background: #eee; }
.btn { background: #c96442; }

Both selectors are identical, so they tie at (0,1,0). The second rule comes later, so it wins — the button comes out orange. This is intentional: when you want to override an existing rule with another rule of the same shape, you write the override below the original.

This is also why the order of <link rel="stylesheet"> tags in your HTML matters. If you load a vendor stylesheet (say, a UI library) and then load your own stylesheet after it, your rules tie or beat the vendor's at equal specificity — and your later rules win. Swap the order and the vendor wins.

Tip

Source order is your friend most of the time. When two rules legitimately tie, ordering them deliberately is much easier to reason about than reaching for higher-specificity selectors or !important.

check your understanding
Two rules in the same file: .title { color: navy } at line 10, .title { color: red } at line 80. Which colour wins on a <h1 class="title">?

!important and inline styles

Two things skip the normal ladder. The first is the keyword !important, written at the end of a declaration:

styles.css
p { color: red !important; }

An !important declaration jumps over every non-!important declaration, no matter the specificity. It is the override hammer — and that is exactly why it causes problems. Once a rule is !important, the only way to beat it is another !important rule with higher specificity, and now you have started an arms race in your own stylesheet.

There are a few legitimate uses: overriding an external library you cannot edit, or a quick utility class meant to be the absolute last word. Inside your own code, reaching for !important almost always means you are losing a specificity fight you should have won. Find the rule that is fighting you and lower its specificity instead, or write your override at a tied specificity later in the file.

The second exception is the inline style attribute. A style written directly on an element beats every selector-based rule, regardless of how specific:

page.html
<p style="color: navy;" class="intro">Hi</p>

.intro { color: red } will not override that style="color: navy" — the inline style wins. The only way to beat it without changing the HTML is !important in your stylesheet. This is one reason inline styles are awkward to manage: they sit at a higher rung than your stylesheet can normally reach.

Watch out

!important on top of !important is not a fix — it is the cascade equivalent of arguing louder. If you are writing your second important, stop and look at the first.

check your understanding
The vendor library you use ships .dialog { padding: 16px !important }. You write .dialog { padding: 24px } in your own file, loaded after theirs. What happens?

Debugging in devtools

Every browser's devtools (right-click → Inspect) has a Styles panel that lists every rule applied to the selected element, in cascade order. Rules that lost to a higher-specificity rule appear with the property crossed out — you can see which rule won and which one you wrote that got beaten.

Two habits make specificity puzzles trivial. First: when "my style isn't applying," open devtools and look at the property. If you do not see your rule listed at all, the selector is not matching anything (typo, wrong class). If you see it crossed out, another rule beat it (specificity or source order) — and the winning rule is right above it. Second: hover over a selector in the Styles panel and the browser shows its specificity score. No counting needed.

check your understanding
You expect your .cta-button rule to apply, but devtools shows the property crossed out. Which is the most useful next step?
check your understanding
Two equally-specific rules set color on the same paragraph. Reordering the rules in the file changes which one wins. What does that tell you?
← prevnext lesson →
KeepLearningcertificate
for completing
CSS fundamentals
0 of 8 read