Learning on Web Dev Open is free for all.

Take It Apart > The cascade, specificity and inheritanceSpecificity without the mythology
Phase 01The cascade, specificity and inheritance45 of 434

Specificity without the mythology

Three numbers, counted left to right, no carrying. Everything else you have heard about it is folklore, including the points system.

Concept16 minAI adversary

Specificity is a triple. The first number counts ids. The second counts classes, attribute selectors and pseudo-classes. The third counts element names and pseudo-elements. Compare left to right and stop at the first difference. That is the entire algorithm.

The "no carrying" part is the bit that trips people. (0,10,0) does not become (1,0,0). Ten classes lose to one id, and so do a hundred, and so do a thousand. This is not a quirk, it is what makes ids a meaningfully different tier rather than just a heavy class, and it is why using ids in CSS is a decision you inherit for the lifetime of the codebase.

The universal selector * and combinators (>, +, ~, the descendant space) contribute nothing. :where() contributes nothing, and neither does anything inside it, which makes it the tool for writing defaults that any later rule can override without a fight. :is() and :not() take the specificity of their most specific argument, which means :is(h1, #title) is as specific as #title even when it matched the h1, a surprising rule that has caused a lot of confused afternoons.

The practical consequence is a strategy rather than a trick: keep specificity low and flat so that later rules can win by source order rather than by escalation. A codebase where everything is one class is easy to override. A codebase with #app .sidebar ul li a.link is one where every new rule has to be at least that heavy, and that is how stylesheets become unmaintainable, not through volume, but through an arms race.

Use the calculator below on the six preset pairs before you type your own. They are chosen to be surprising. Then go and paste in three selectors from your own project. If any of them is above (0,3,0), you have found something worth simplifying, and simplifying it now is much cheaper than simplifying it in a year.

What a selector is actually worth

parsed, not counted
#nav a
1
a
0
b
1
c
Head to headboth apply to the same element
#nav a
1
a
0
b
1
c
← wins
nav a.link.active.current
0
a
3
b
2
c

Six pairs that catch people out

Five things against two. The id column decides before the class column is read.

#nav a wins at 1,0,1 against 0,3,2, decided in column a: ids. Once a column differs, nothing in the columns after it is read at all.

Type any selector. The preset pairs are the ones that catch people, try to predict each before revealing it.

One more note on !important, since it belongs here even though it is technically step one. It is not a specificity booster, it moves the declaration into a different origin band entirely. That is one reason an important declaration in a stylesheet beats an inline style, and why the only thing that beats an important author declaration is another important author declaration with higher specificity. Once a codebase has two of them arguing, the only way out is to remove both.

Worth reading

You should now be able to

  • Calculate specificity for any selector by hand
  • Explain why 100 classes never beat one id
  • Use :where() to write a rule that deliberately loses
  • Predict which of two selectors wins and by which component
Ask the community

Loading…