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 type | Examples | Weight |
|---|---|---|
Inline style="" attribute | style="color: red" | Highest (beats any selector in a stylesheet) |
| ID | #header | High |
| Class, attribute, pseudo-class | .card, [type="text"], :hover, :nth-child() | Medium |
| Element, pseudo-element | div, ::before | Low |
| 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.
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.