CodeOath
← All posts
HTML & CSS70 min total · 18 parts

CSS Fundamentals: The Box Model, Specificity, Positioning, and Layout

Contents — Part 7 of 18: Specificity: How CSS Breaks Ties
Part 7 of 18 · ~2 min

Specificity: How CSS Breaks Ties

When multiple non-!important rules target the same element and set the same property, the browser computes a specificity score for each selector and the highest one wins.

Specificity is a three-part score, ranked from strongest to weakest:

Selector typeExamplesWeight
Inline style="" attributestyle="color: red"Highest (beats any selector in a stylesheet)
ID#headerHigh
Class, attribute, pseudo-class.card, [type="text"], :hover, :nth-child()Medium
Element, pseudo-elementdiv, ::beforeLow
Universal selector, combinators*, >, +, ~None — adds zero specificity

A single ID selector beats any number of classes; a single class beats any number of element selectors — specificity compares tiers first, and only breaks a tie within a tier by counting how many selectors of that tier appear:

#nav a { color: blue; }          /* 1 ID, 0 classes, 1 element  → wins */
nav ul li a.active { color: red; } /* 0 IDs, 1 class, 3 elements → loses, despite looking "more specific" */

The second rule has more selectors written out, but zero IDs beats any number of classes and elements — specificity is never about how long a selector looks, only about which tiers it touches.

Two special selectors change how their contents count, which trips people up:

:is(.card, .panel) p { color: navy; }  /* :is() takes the specificity of its MOST specific argument (.card = one class) */
:where(.card, .panel) p { color: navy; } /* :where() always contributes ZERO specificity, regardless of its arguments */

:where() exists specifically so you can write convenient grouped selectors (for a CSS reset, a design system's base styles) without them ever being hard to override later — anything, even a single class, beats a :where(...) block.

Why this is the real motivation behind BEM

Naming conventions like BEM (.block__element--modifier) exist to sidestep specificity entirely: every selector stays at the same tier (exactly one class), so source order, not an escalating specificity arms race, decides which rule wins. Once a codebase has a mix of ID selectors, deeply nested descendant selectors, and the occasional !important, "which rule actually applies" stops being predictable from reading the CSS alone — flat, single-class selectors keep it predictable indefinitely.