skip to content

Selectors and Specificity

Everything that decides which declaration actually applies to an element: what a selector matches, how its weight is scored, and how the cascade breaks the remaining ties. Interviewers open here because "why isn't my style applying" is the single most common real-world CSS bug.

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

explore

questions

page 1 of 2

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

In CSS, which kinds of properties are inherited by default and which are not, and how does inheritance differ from the cascade?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Text-related properties inherit by default: color, the font properties, line-height, letter-spacing, text-align, white-space, list-style, visibility and cursor. Box and painting properties do not: margin, padding, border, background, width, height, display, position, overflow.

open as a page

What does the CSS @layer at-rule do, and how does a layer's position affect which declaration wins?

level: juniorimportance: must knowfreq 48%

basics

~20 s

@layer groups CSS rules into named layers that the cascade compares before it looks at specificity. A declaration in a later-declared layer beats one in an earlier layer, even when the earlier selector is far more specific.

open as a page

In CSS, why does a ::before rule render nothing unless it declares the content property, and what does content: "" actually produce?

level: juniorimportance: must knowfreq 78%

basics

~20 s

A ::before box exists only if the content property generates it — content's initial value computes to none on ::before and ::after, so without it no box is created at all. content: "" generates a real but empty inline box you can then style.

open as a page

In CSS, how do the selectors `.card.featured`, `.card .featured`, and `.card, .featured` differ in what they match?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Written together, .card.featured matches one element carrying both classes. With a space, .card .featured matches a .featured element inside a .card. With a comma, .card, .featured is a selector list: two independent selectors sharing one declaration block.

open as a page

In CSS, what do the selectors `div p`, `div > p`, `div + p`, and `div ~ p` each match?

level: juniorimportance: must knowfreq 85%

basics

~20 s

div p matches a p at any depth inside a div. div > p matches only a p that is a direct child. div + p matches the p immediately after a div, sharing a parent. div ~ p matches any later p sibling.

open as a page

In CSS, when two declarations set the same property on the same element, what does the browser compare, and in what order, to decide which one applies?

level: middleimportance: must knowfreq 72%

basics

~20 s

The cascade compares origin and importance first, then shadow-tree context, then inline style attribute, then cascade layer, then specificity, and finally source order. Specificity is only reached when every earlier criterion ties, which is why a low-specificity important declaration still wins.

open as a page

In CSS, what is the difference between the global keywords inherit, initial, unset, and revert?

level: middleimportance: must knowfreq 60%

basics

~20 s

inherit takes the parent's computed value; initial uses the property's spec-defined initial value; unset means inherit for inherited properties and initial for the rest; revert rolls the property back to the user-agent or user value, discarding author styles.

open as a page

What does the CSS :has() pseudo-class match, and why is it described as a relational or "parent" selector?

level: middleimportance: must knowfreq 62%

basics

~20 s

:has() matches an element based on what it contains or what follows it: .card:has(img) styles the card itself, not the image. Because the styled subject is the outer element, it gives CSS the parent selection that descendant combinators never could.

open as a page

In CSS, how does the specificity of :is() differ from :where(), and how is each one computed?

level: middleimportance: must knowfreq 72%

basics

~10 s

:is() takes the specificity of its most specific argument, applied to every match. :where() always contributes zero specificity no matter what it contains. Both match identically; only their weight in the cascade differs.

open as a page

In CSS, where do declarations that are not inside any @layer rank compared to layered ones?

level: middleimportance: must knowfreq 42%

basics

~20 s

Unlayered normal declarations behave as a final, implicit layer that comes after every named layer, so they beat layered rules of any specificity. Wrapping your own CSS in a layer while a vendor stylesheet stays unlayered therefore lets the vendor win.

open as a page

In CSS, how does the an+b formula in :nth-child() work, and why can p:nth-child(2) match nothing on a page full of paragraphs?

level: middleimportance: must knowfreq 70%

basics

~20 s

:nth-child(an+b) substitutes n = 0, 1, 2, … and matches elements at the resulting 1-based positions among all siblings. The count ignores the rest of the selector, so p:nth-child(2) matches only when the second child happens to be a p.

open as a page

In CSS, what does :focus-visible match that :focus does not, and why was it added to the language?

level: seniorimportance: must knowfreq 55%

basics

~20 s

:focus matches any focused element; :focus-visible matches only when the browser judges a focus indicator should be shown, which in practice means keyboard focus and text-entry fields. It exists so removing mouse-click rings no longer strips keyboard users of their focus indicator.

open as a page

What is a browser's user-agent stylesheet, and why does an ordinary CSS rule you write override it without needing !important?

level: juniorimportance: should knowfreq 52%

basics

~20 s

The user-agent stylesheet is the browser's own set of default styles for HTML elements — heading margins, list padding, link colour and underline, form-control appearance. It is the weakest origin in the cascade, so any normal author declaration for the same property beats it on origin alone.

open as a page

What does the CSS selector `.card :is(h2, h3)` match, and what would you write instead if :is() were not available?

level: juniorimportance: should knowfreq 48%

basics

~20 s

It matches any h2 or h3 that is a descendant of an element with class card — the same elements as the longhand list .card h2, .card h3. :is() matches an element if it matches any selector in its argument list, so the shared prefix is written once.

open as a page

In native CSS nesting, what does the & nesting selector refer to, and what does a nested rule that omits & match?

level: juniorimportance: should knowfreq 55%

basics

~20 s

& stands for the enclosing rule's selector list. A nested rule written without & gets an implied descendant combinator, so .card { p { ... } } styles paragraphs inside .card, exactly like .card p.

open as a page

In CSS, what does the :checked pseudo-class match, and how can it drive styling elsewhere on the page without JavaScript?

level: juniorimportance: should knowfreq 50%

basics

~20 s

:checked matches checkboxes and radio buttons that are currently checked and option elements that are selected. It tracks live state, not the source attribute, so pairing it with a sibling combinator lets a toggle restyle nearby elements with no script.

open as a page

What is the difference between the CSS selectors :before and ::before, and which pseudo-elements refuse the single-colon form?

level: juniorimportance: should knowfreq 52%

basics

~20 s

Both forms select the same generated box. Double colon is the modern syntax that distinguishes pseudo-elements from pseudo-classes; single colon survives only for the four CSS2 pseudo-elements (::before, ::after, ::first-line, ::first-letter). Anything newer requires two colons.

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

In CSS, why does line-height: 1.5 on a container behave differently for descendants than line-height: 150%?

level: middleimportance: should knowfreq 42%

basics

~20 s

Inheritance passes the parent's computed value. A unitless line-height computes to the number itself, so each descendant multiplies it by its own font-size; a percentage resolves to a fixed length on the parent, and every descendant inherits that same length.

open as a page

A page sets font-family on body, yet the site's buttons and inputs still render in the browser's default font. Why does inheritance not reach them, and what is the standard fix?

level: middleimportance: should knowfreq 52%

basics

~10 s

The user-agent stylesheet declares font on form controls, and inheritance only applies when no declaration reaches an element. The fix is to opt back in explicitly: button, input, select, textarea { font: inherit; }.

open as a page

How does the browser establish the order of CSS cascade layers, and what happens when you re-open a named @layer block later in the same stylesheet?

level: middleimportance: should knowfreq 34%

basics

~20 s

Layer order is fixed by where each layer name first appears, not by where its rules sit. Re-opening a named layer appends declarations to the existing layer and never moves it, which is why teams declare the full order once at the top with a @layer statement.

open as a page

In native CSS, can an @media block be written inside a style rule, and what do the declarations inside it apply to?

level: middleimportance: should knowfreq 38%

basics

~20 s

Yes. Conditional at-rules such as @media, @supports, and @container may be nested inside a style rule, and their declarations apply to that rule's own elements when the condition matches, as if wrapped in a nested & { ... } block.

open as a page

How is the specificity of a native CSS nested rule computed, and why can a nested rule be more specific than the selector it appears to write?

level: middleimportance: should knowfreq 42%

basics

~20 s

A nested rule's & carries the specificity of the most specific complex selector in the parent rule's selector list, exactly like :is(). One ID anywhere in that list makes every rule nested inside it ID-specific.

open as a page

In Sass, `.card { &__title { } }` compiles to `.card__title`. Why does that pattern not work with native CSS nesting, and what forms of & are valid there?

level: middleimportance: should knowfreq 45%

basics

~20 s

Sass treats & as text and glues it to the suffix, producing .card__title. Native CSS & is a real selector that cannot be concatenated with an identifier, so &__title is an invalid selector and the browser drops the rule.

open as a page

In CSS, what does the :not() pseudo-class match, and how does :not(.a, .b) differ from :not(.a):not(.b)?

level: middleimportance: should knowfreq 45%

basics

~10 s

:not() matches any element that does not match the selector given to it. With a comma-separated list, :not(.a, .b) matches elements carrying neither class — identical in effect to chaining :not(.a):not(.b), just written once.

open as a page

Why does adding a ::after rule with a content value to an <img> or an <input> usually render nothing?

level: middleimportance: should knowfreq 46%

basics

~20 s

Those are replaced elements: their box is filled by an external resource or a browser-supplied widget rather than by child boxes, so there is nowhere for a generated child box to go. Wrap the element in a non-replaced one and style that instead.

open as a page

In CSS, what is the difference between the attribute selectors `[href^="/docs"]`, `[href$=".pdf"]`, `[href*="api"]`, `[class~="btn"]`, and `[lang|="en"]`, and what does adding the `i` flag do?

level: middleimportance: should knowfreq 50%

basics

~20 s

They are attribute value operators: ^= matches a prefix, $= a suffix, *= a substring anywhere, ~= one whitespace-separated word in the value, and |= an exact value or one followed by a hyphen. Adding i before the closing bracket makes the comparison case-insensitive.

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

showing 1–30 of 43