DevPath · Learn to code ESPTEN

CSS: selectors, cascade and specificity

Specificity and !important

What is specificity?

Here's the other half of the "why isn't my CSS applying?" mystery. When two rules of equal origin clash, the last one doesn't just win by default: the one with higher specificity wins, a measure of the selector's weight. It is calculated by counting three categories, which you can picture as a three-column number (A, B, C):

Category Counts Weight
id (#header) nº of ids 100
class / attribute / pseudo-class (.notice, [type], :hover) nº of them 10
element / pseudo-element (div, ::before) nº of them 1

The universal selector * and the combinators ( , >, +, ~) do not contribute specificity.

Compared examples

/* specificity 0,0,1  → 1 */
p { color: black; }

/* specificity 0,1,1  → 11 */
p.featured { color: green; }

/* specificity 0,1,0  → 10 */
.featured { color: blue; }

/* specificity 1,0,0  → 100 */
#notice { color: red; }

On a <p class="featured" id="notice">, #notice (100) would win because the id weighs more than any combination of classes or elements. And between p.featured (11) and .featured (10), p.featured wins for being more specific, even though .featured is declared later.

The comparison is category by category, from left to right: a single id beats any number of classes, and a single class beats any number of elements. That's why 10 classes (0,10,0) do not beat an id (1,0,0).

⚠️ CLASSIC TRAP: id beats class, and class beats tag, regardless of order. Picture a menu with .link { color: gray } for the normal state and #menu a { color: tomato } to highlight it: the id wins and the links come out orange, even though the class is written later. It's not magic or a browser bug; it's the left column outranking the right one. When a rule "doesn't work", the first thing to ask is which other rule is beating it on specificity.

!important: the last resort

Adding !important to a declaration takes it out of the normal specificity competition and places it on top:

.button { color: blue !important; }
#header .button { color: red; }   /* loses: the !important wins */

It works, and that's exactly why it's tempting: when something "won't apply", dropping in an !important fixes it instantly. The catch is that it's a quick fix that costs you later, bad practice except in very specific cases:

Good practice is to keep specificity low and uniform (rely on classes, avoid ids for styling and don't over-nest selectors). That way the cascade and the order of appearance are enough for the correct rule to win, and !important stays where it belongs: a last resort, not your go-to hammer for everything.

Put this into practice

DevPath is a hands-on course: you read the theory here; in the app you put it into practice with exercises that really run, offline.

Start free in the app →
← The cascade and inheritanceView the module →