VS Vikesh Singh

Courses CSS Lesson 14

CSS · LESSON 14 OF 18

Specificity and the Cascade: Why Your CSS Isn't Applying

· 4 min read

At some point in every CSS learner's journey, this exact frustration happens.

At some point in every CSS learner's journey, this exact frustration happens: you write a rule, you're certain the selector is correct, but the style simply refuses to apply, and some other, seemingly unrelated rule wins instead. This lesson explains exactly why that happens, by finally properly explaining the "cascading" part of Cascading Style Sheets, a word introduced back in lesson one and deliberately left unexplained until now, because it needs everything you've learned about selectors to make real sense.

How The Cascade Decides

When multiple CSS rules target the same element and try to set the same property, the browser has to decide which one wins. It does this using three factors, checked in order: importance, specificity, and source order.

Specificity is a kind of scoring system based on what type of selector you used. Roughly, from weakest to strongest: element selectors and pseudo-elements score the lowest, classes, attribute selectors, and pseudo-classes score higher, and IDs score higher still. Inline styles (written directly in the HTML style attribute) beat all of those, and !important beats everything else entirely.

p { color: blue; }
.intro { color: green; }
#main-paragraph { color: purple; }
<p id="main-paragraph" class="intro">Hi, I'm Vikesh Singh.</p>

All three rules target this same paragraph. The element selector p has the lowest specificity, the class .intro is stronger, and the ID #main-paragraph is the strongest of the three. The text renders purple, because the ID selector wins regardless of the order the rules were written in the file. This is exactly why lesson two recommended favoring classes over IDs for everyday styling: IDs are so strong that they're hard to override later without reaching for an even more specific selector or resorting to !important, which creates its own problems.

Source Order As Tiebreaker

When two selectors have equal specificity, the tiebreaker is source order: whichever rule appears later in the CSS (or later among multiple linked stylesheets) wins.

.button { background: gray; }
.button { background: blue; }

Here, both selectors are identical in specificity (a single class each), so the second rule, appearing later in the file, wins, and the button renders blue. This explains why file order matters so much in CSS, and it's exactly the mechanic lesson eleven relied on when writing base styles first and media query overrides afterward: equal specificity, later wins.

Combined selectors add their specificity together. A selector like .card .title is slightly more specific than .title alone, because it's two class selectors combined rather than one, even though neither is an ID.

.title { color: black; }
.card .title { color: navy; }

A .title element inside a .card renders navy, because .card .title has higher combined specificity, regardless of which rule appears first or second in the file.

The Important Flag

!important is a special flag you can add to a declaration to forcibly override normal specificity rules entirely.

.warning-text {
  color: red !important;
}

This will beat almost any other rule for that property, including IDs, unless another !important rule with even higher specificity also exists. The near-universal advice from experienced developers is: avoid !important in regular project code. It's extremely tempting as a quick fix when a style "won't apply," but it creates a style that's very hard to override later, and it tends to spread, because once one !important is in a file, overriding it later often seems to require another one, leading to a messy arms race. The honest fix for a specificity fight is almost always to understand why the other rule is winning, and adjust your own selector's specificity deliberately, not to reach for !important as a shortcut.

Inheritance Versus Cascade

Inheritance is a separate, gentler concept worth distinguishing from the cascade: some properties (like color and font-family) are automatically passed down from a parent element to its children if the children don't set their own value, while others (like border and padding) are not inherited by default, since inheriting a border onto every nested child would rarely make sense. This is why setting font-family once on body conveniently affects the whole page, while setting border on body would not cascade down the same way.

A practical, reliable debugging method for "my CSS isn't applying" problems: open developer tools, click the element, and look at the Styles panel. Browsers show every matching rule for that element, with overridden (losing) declarations visibly crossed out, directly revealing which rule won and why, without any guesswork.

Practical Tips And Exercise

Practical tip: build the habit of keeping specificity low and flat across a project (mostly single classes, rarely IDs, almost never !important), and let source order do the organizing work instead. Flat, low-specificity CSS is dramatically easier to override and maintain as a project grows.

Give it a try: create a paragraph with both a class and an ID, write three conflicting color rules targeting it, one via the element, one via the class, one via the ID, and predict which color will win before checking in the browser. Then open developer tools and confirm your prediction by seeing which declarations are crossed out.