skip to content

The Specificity Algorithm

How the browser scores a selector as an (ID, class, element) triple and compares those tuples left to right, plus where inline styles and !important sit outside the scale. Expect to be handed three competing selectors and asked which one wins.

part ofCSSoverview, primer and where to startread it →
on this pageshow

questions

4

How does a browser compute a CSS selector's specificity, and how are two selectors' specificity values compared?

level: juniorimportance: must knowfreq 85%

answer

  1. three buckets, not one number
  2. IDs, then classes, then types
  3. columns compared left to right
  4. never carries like base ten
  5. exact tie falls to source order

basics

~20 s

Specificity is a three-part count: ID selectors, then class, attribute and pseudo-class selectors, then type selectors and pseudo-elements. Browsers compare the three columns left to right, and the first column that differs decides; identical values fall back to source order.

solid answer

~40 s

Every selector gets scored as a triple, usually written `1-2-1`: the first column counts ID selectors, the second counts class selectors, attribute selectors and pseudo-classes, and the third counts type selectors and pseudo-elements. The universal selector `*` and the combinators (` `, `>`, `+`, `~`) add nothing. Comparison is column by column, not arithmetic: the browser looks at the ID column first, and only if it ties does it look at the class column, then the type column. That is why `#hero h2` at `1-0-1` beats `.page .content .title` at `0-3-0` — no number of classes ever climbs into the ID column. If two selectors produce exactly the same triple, the declaration that comes later in source order wins.

code

css · 4 lines
css
a                  { color: black; }  /* 0-0-1 */
.nav a             { color: navy; }   /* 0-1-1 */
nav > .nav a:hover { color: teal; }   /* 0-2-2 */
#sidebar .nav a    { color: red; }    /* 1-1-1 */

go deeper

for a junior

Be ready to score a handful of selectors on the spot and say which wins. Know the three buckets — IDs, classes, elements — and that ties are broken by whichever rule comes last.

for a middle

Explain that the comparison is column-wise rather than a summed number, and show why eleven classes still lose to one ID. Be precise about which selector parts score zero.

for a senior

Show you can debug with it: reason about which declaration actually won on a real page, and explain that specificity only decides among declarations that already tied on origin, importance and layer.

for a principal

Own the consequence for a codebase: high-specificity selectors are a one-way ratchet that forces every future override higher, so the interesting decision is which selector shapes a team allows at all.

## What specificity is for When several declarations set the same property on the same element, the browser has to choose one. It sorts the candidates in fixed steps: first by origin and importance (which stylesheet it came from, and whether the declaration is marked `!important`), then — when cascade layers are in play — by layer order, then by **specificity**, and finally by source order. So specificity is not a general measure of how "strong" a rule is; it is one tiebreak that only runs between declarations that already tied on the earlier steps. ## The three columns The browser scores each selector as a three-part value, conventionally written `1-2-1` or `(1,2,1)`: - **Column A — IDs**: one for each ID selector, such as `#hero`. - **Column B — classes**: one for each class selector (`.card`), each attribute selector (`[disabled]`, `[data-state="open"]`), and each pseudo-class (`:hover`, `:nth-child(2)`). - **Column C — types**: one for each type selector (`div`, `a`, `input`) and each pseudo-element (`::before`, `::marker`). Nothing else contributes. The universal selector `*` scores zero, and so do all the combinators — the descendant space, `>`, `+`, `~`. ```css a { } /* 0-0-1 */ .nav a { } /* 0-1-1 */ nav > .nav a:hover { } /* 0-2-2 */ #sidebar .nav a { } /* 1-1-1 */ ``` ## Comparison is column by column, not arithmetic This is the part candidates get wrong. The triple is not a base-ten number and the columns are never summed. The browser compares column A of both selectors; if they differ, it is done. Only on a tie does it compare column B, and only on another tie does it compare column C. The consequence is that the columns are effectively infinite. A selector with eleven classes scores `0-11-0` and still loses to a single `#id` at `1-0-0`. Popular "100 points for an ID, 10 for a class, 1 for an element" mnemonics give the right answer for small selectors and the wrong answer as soon as one column exceeds nine, which is exactly the case an interviewer will hand you. ## A worked example Given these three rules matching the same `<h2 class="title">` inside `#hero`: ```css #hero h2 { color: red; } /* 1-0-1 */ .page .content .title { color: blue; } /* 0-3-0 */ h2.title { color: green; } /* 0-1-1 */ ``` The first rule wins with red. It has a 1 in the ID column, and the other two have a 0 there, so the class column is never even consulted. Between the second and third rules, blue would beat green, because `0-3-0` and `0-1-1` tie on A and then differ on B. ## Ties fall through to source order When the triples are identical, the last declaration wins — "last" meaning last in the document's final source order, which spans all stylesheets in the order the document pulls them in, not just the file you happen to be looking at. ## Traps worth naming out loud - **Depth is not specificity.** `body div ul li a` is `0-0-5` and loses to a bare `.active` at `0-1-0`. A longer selector is not automatically a stronger one. - **A declaration in the `style` attribute has no triple at all.** It is not selector-matched, and the cascade places it above all normal author rules regardless of how specific your selector is. - **`!important` is not a fourth column.** Importance is decided in an earlier cascade step; specificity then breaks ties among the declarations that survived it. - **Selector lists are scored independently.** In `h2, .title { color: red }` the two complex selectors are scored separately (`0-0-1` and `0-1-0`); the score that counts for a given element is the one for the selector that actually matched it. - **Matching more elements means nothing.** Specificity is a property of the selector text alone — it does not depend on the page, the element, or the property being set. ## How to answer the puzzle version When you are handed three selectors and asked which wins, do it out loud and mechanically: read each selector left to right, tally IDs, then classes/attributes/pseudo-classes, then types/pseudo-elements, write the three triples down, and compare column by column. Saying "one hundred and one versus thirty" invites the follow-up that breaks the mnemonic; saying "1-0-1 versus 0-3-0, the ID column decides" does not.

  • Does a deeply nested selector like `body div ul li a` beat a single class selector?
    No. It scores `0-0-5` — five type selectors and nothing else, because the descendant combinators contribute zero. A single `.active` scores `0-1-0`, and the class column is compared before the type column, so the class wins. Depth of the selector and number of elements matched have no bearing on specificity.
  • How is specificity computed for a selector list such as `h1, .title, #main`?
    Each complex selector in the list is scored independently: `0-0-1`, `0-1-0` and `1-0-0`. The declarations apply to every element matched by any of them, and the specificity used for a given element is that of the selector that actually matched it — the list does not get a single combined score.
  • At what point in the cascade does the specificity comparison actually run?
    Last but one. The browser first sorts by origin and importance, then by cascade layer order if layers are used; specificity only compares the declarations still tied after that, and an exact specificity tie falls through to source order. So a more specific selector can still lose to a less specific one that won an earlier step.

Think of it as sorting version numbers rather than adding up points: 1.0.1 beats 0.3.0 for the same reason 1-0-1 beats 0-3-0 — you compare the leftmost segment first and never carry.

saying these in an interview costs you the question

  • Says an ID is worth 100 points and classes add up
  • Claims the longer or deeper selector always wins
  • Counts the universal selector or combinators as one point
  • Treats !important as a fourth specificity column
  • Thinks the selector matching fewer elements is more specific

context

open as a page

A third-party widget sets its background colour through the style attribute on the element. Why can't a more specific CSS selector override it, and what can?

level: middleimportance: should knowfreq 50%

basics

~20 s

The style attribute is not scored by specificity at all: the cascade ranks its declarations above every normal rule in your stylesheets, however specific the selector. Only a declaration marked !important beats a normal inline one.

open as a page

In CSS specificity, which parts of a selector land in the ID, class, and type columns — and which parts contribute nothing at all?

level: middleimportance: should knowfreq 62%

basics

~10 s

ID selectors fill the first column. Class selectors, attribute selectors and pseudo-classes fill the second. Type selectors and pseudo-elements fill the third. The universal selector and all combinators contribute nothing.

open as a page

Two CSS rules with identical specificity set the same property on one element. Which declaration wins, and what determines that order in a bundled stylesheet?

level: seniorimportance: should knowfreq 40%

basics

~20 s

The declaration that comes later wins. Order means position in the document's final flattened stylesheet — the order stylesheets are linked, imported or injected, and the order files are concatenated inside a bundle — not the order of files in your editor.

open as a page