The Cascade & Specificity
Discover why some CSS rules win over others — and build a reliable mental model for debugging 'why isn't my style applying?'
You've written a CSS rule, refreshed the browser, and… nothing changed. This is one of the most common moments of confusion for CSS beginners, and it has a very logical explanation.
CSS stands for Cascading Style Sheets. When two or more rules compete to style the same element, CSS uses a well-defined algorithm — the cascade — to pick the winner. The cascade considers three things in order: specificity, source order, and !important. Once you understand these, mysterious overrides stop feeling like magic and start feeling like a puzzle you can always solve.
The cascade is like a job interview panel
Picture three judges scoring candidates. The first judge (Specificity) gives a score based on how precisely each rule targets the element. If scores are tied, the second judge (Source Order) picks whoever came last — that's why p { color: tomato; } wins over an earlier p { color: steelblue; } when selectors are identical. The third judge (!important) has veto power and overrides everything — but using that power carelessly causes chaos.
#Specificity: The Score
Specificity is a score that measures how precisely a selector targets an element. CSS calculates it using three buckets — (A, B, C):
| Bucket | What counts | Example | |--------|-------------|-------| | A | ID selectors | #hero | | B | Class, attribute, pseudo-class | .btn, [type], :hover | | C | Element / pseudo-element | p, h1, ::before |
Compare bucket by bucket, left to right. One ID (1,0,0) beats any number of classes (0,99,0). And above all selectors sits the inline style, written directly on the element — it has no selector at all, so it auto-wins.
Full ranking, lowest to highest: `` element selector < class/attribute/pseudo-class < ID < inline style ``
/* (0,0,1) — one element */
p { color: steelblue; }
/* (0,1,0) — one class */
.intro { color: goldenrod; }
/* (1,0,0) — one ID */
#hero { color: tomato; }!important: avoid it — it's a trap
Adding !important after a value (color: seagreen !important;) yanks that declaration above the entire specificity system — it even beats inline styles. It feels like a quick fix, but once you use it the only way to override it is with another !important, and you end up in an arms race. Prefer writing a more specific selector instead. Reserve !important for true utility classes like .hidden { display: none !important; } where an unconditional rule makes semantic sense.
#Inheritance
Some CSS properties flow down from a parent element to its children automatically — this is inheritance. Text properties like color, font-family, and line-height inherit by default. Box-model properties like margin, padding, border, and width do not.
Inheritance is a feature: set font-family on body and every text element on the page picks it up without you writing a rule for each one. If you ever need to force a non-inheriting property to inherit, set its value to the keyword inherit.
#A Mental Model for Debugging
When a style isn't applying, walk through this checklist:
- Is the selector matching? Open browser DevTools (F12 or right-click → Inspect). The Styles panel shows every competing rule — crossed-out declarations are the losers.
- Is something more specific winning? Look for an ID selector or inline style overriding your class.
- Is source order the issue? A later rule with the same specificity silently overwrites yours.
- Is `!important` involved? DevTools labels these clearly.
- Is it an inherited property? Check whether the property you expect to inherit actually does (text properties: yes; box properties: no).
DevTools is your fastest path to answers — the winning value is shown in bold; everything else is struck through.
Specificity is per-rule, not cumulative
Writing .nav .nav .nav .nav in four separate rules does not 'bank' specificity. Each rule is scored independently when the cascade runs. Only selectors within a single rule combine their scores.
Given these two CSS rules, what colour will a paragraph with class='highlight' render in? p { color: steelblue; } .highlight { color: goldenrod; }
Key takeaways
- The cascade resolves conflicts using specificity first, then source order — later rules win ties.
- Specificity is scored as (IDs, classes/attributes/pseudo-classes, elements) — a higher score wins regardless of order.
- Inline styles beat any stylesheet selector; !important beats everything — use both sparingly.
- Inheritable properties (color, font-*) flow from parent to child automatically; box-model properties do not.
- Browser DevTools is the fastest debugging tool — look for crossed-out declarations to find what's overriding your rule.
Given the HTML <p id="hero" class="intro">Hi</p> and the CSS below, what color does the paragraph render?
p { color: steelblue; }
.intro { color: goldenrod; }
#hero { color: tomato; }This code has a bug — the developer expected the .btn-primary background to win over the button rule, but the button stays steelblue. What's wrong?
button {
background-color: steelblue;
}
.btn-primary {
background-colour: seagreen;
}The value below yanks a declaration above the entire specificity system — even above inline styles. The lesson warns to reserve it for true utility classes. Complete the .hidden rule with that keyword/flag.
.hidden { display: none ; }
For <p class="note">, these four rules all set color and all match. Order them from LOWEST cascade priority (top) to HIGHEST priority (bottom) — the bottom rule's color is what actually renders.
p { color: steelblue; } /* (0,0,1) element */#lead { color: tomato; } /* (1,0,0) ID */p { color: seagreen !important; } /* !important wins */.note { color: goldenrod; } /* (0,1,0) class */The button below should have a green background. Three rules are competing for it. Make the .btn-primary rule win — without using !important or adding inline styles. You may rewrite or remove any existing rule.
Try it live — edit the code and hit Run to see it rendered: