The Cascade · stage 05
Specificity
A scoring system decides which rule wins, and it is not the one that appears last.
Stage 05 of six/The Cascade, piece 1 of 3/Full piece/Next: Inheritance
The score that overrides source order
When two CSS rules target the same element and set the same property, source order is not the final word. The cascade resolves conflicts in layers, and specificity sits above position in the stylesheet: a rule that appears earlier wins if its selector outscores the one that follows.
The W3C's CSS specification describes specificity as a weight calculated from the structure of a selector, expressed as three independent counters. The numbers are conventionally written as a triplet — (A, B, C) — where A counts ID selectors, B counts class selectors, attribute selectors and pseudo-classes, and C counts type selectors and pseudo-elements. The counters do not roll over: ten class selectors do not equal one ID. A specificity of (0, 10, 0) is still lower than (1, 0, 0) regardless of the gap in raw numerics, because the triplet is compared column by column from left to right.
In practice: #main scores (1, 0, 0). .card.active scores (0, 2, 0). ul li a:hover scores (0, 1, 3). The ID wins over either of those other two, however many classes or type selectors pile up against it.
The universal selector (*) contributes nothing — (0, 0, 0) — which is why it can be used liberally for layout resets without disrupting any authored rule. The :not(), :is() and :has() pseudo-classes are themselves ignored, but their arguments count at full value: :not(#header) is (1, 0, 0) just as #header is.
Inline styles occupy a slot above the three counters. The specification models them as (1, 0, 0, 0) if you extend the triplet to a quad, placing them above any selector-based rule. Only !important overrides them, and !important does so by moving the declaration into a separate, higher-priority origin entirely — which is why mixing !important into a stylesheet tends to escalate into a specificity arms race that becomes progressively harder to reason about.
Inherited values sit below everything. A property flowing down from a parent through inheritance carries no specificity at all; the moment any matching rule — even a low-scoring type selector — targets the element directly, it wins.
Where browsers agree, and where they used to differ
What has historically varied is behaviour at the edges: the specificity of :is() and :has() arguments was not always specified with the precision the current Selectors specification provides, and older engines disagreed on how to handle complex selectors inside those pseudo-classes. Modern engines — Blink, Gecko, and WebKit — converge on the same results for well-formed selectors.
The :where() pseudo-class, a later addition, is defined to contribute zero specificity regardless of its arguments. It exists precisely to allow reusable selectors without carrying specificity cost — a facility that Håkon Wium Lie and Bert Bos could not have anticipated when the cascade's foundations were laid in the mid-1990s, but which fits the original intuition that authors should be able to provide fallbacks cheaply.
One practical consequence of the scoring model is that specificity belongs to the selector, but the cascade compares it property by property. A high-specificity rule setting color and background will beat a lower-specificity rule changing only color,yet a rule that matches or beats its score can override color alone and leave background untouched. To change any value it sets, you have to match or beat its score.
When two CSS rules target the same element and set the same property, source order is not the final word.
That pressure is what drives the proliferation of ID selectors in large stylesheets — an ID in a base stylesheet is difficult to beat without adding another ID, and so the score creeps up until !important seems like the only exit. Methodologies such as BEM (Block, Element, Modifier) respond to this by keeping every selector at class level, holding specificity flat across the whole codebase so source order can do its ordinary work.
The deeper lesson is that specificity is not a bug introduced by implementation decisions: it is a deliberate design choice in the cascade, intended to give authors fine-grained control over which rule should win for which element. Understanding it as a scoring system, read column by column, is the only model that reliably predicts which rule the browser will apply — and that predictability is what separates intentional styling from an endless process of adding selectors until something sticks.