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

A CSS rule written out large on a whiteboard with parts circled in marker
Stage 05 · The Cascade(A, B, C) — the three-column triplet: A = IDs, B = classes/attributes/pseudo-classes, C = types/pseudo-elements

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.

A printed family-tree style diagram on paper with lines traced in pen
Also in The CascadeSome properties pass down the tree and some do not, and the list is arbitrary history. Inheritance

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.

Stage 05

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.

A terminal window close enough to read a request and its response headers
Stage 05 · The Cascade

The stage in a room: a scoring system decides which rule wins, and it is not the one that appears last.