AniUI Academy

Score CSS selector specificity

hardDOM & Utilities+350 XPPractice

"Why isn't my style applying?" is nearly always specificity. Being able to score a selector by hand is what turns that question into a two-second answer, and linters and CSS-in-JS tools compute exactly this triple.

Implement specificity(selector) returning [a, b, c], where a counts id selectors, b counts class selectors, attribute selectors and pseudo-classes, and c counts type (element) selectors and pseudo-elements. Assume a single selector with no commas.

Count each #id into a. Count each .class, each [...] attribute selector, and each single-colon pseudo-class such as :hover or :nth-child(2n+1) into b; the arguments of a functional pseudo-class are ignored. Count each type selector such as div and each double-colon pseudo-element such as ::before into c. The universal selector * and the combinators >, +, ~ and descendant whitespace count for nothing.

:not(), :is() and :has() add nothing for themselves, but the selectors written inside their parentheses are counted as if they appeared in their place — so li:not(.active) scores [0, 1, 1]. Note that anything inside an attribute selector, including a # in [href^="#"], is part of the attribute selector and is never counted separately.

Arguments

selectorstring
A single CSS selector with no commas. Combinators, pseudo-classes, pseudo-elements and attribute selectors are supported.

Returns

[number, number, number] — A three-tuple [ids, (classes + attrs + pseudo-classes), (types + pseudo-elements)] — the triple CSS compares to resolve conflicts.

Examples

specificity("#nav .item a");
// => [1, 1, 1]

One id, one class, one element selector.

specificity('a[href^="#"]:hover');
// => [0, 2, 1]

The # inside the attribute selector does not count as an id.

specificity("li:not(.active) > span::before");
// => [0, 1, 3]

:not() itself scores nothing, but .active inside it counts; ::before is a pseudo-element.

What it has to do

  • Return a three element array [ids, classes, types] of numbers.
  • Count #id into the first slot, and .class, [attr] and :pseudo-class into the second.
  • Count element names and ::pseudo-elements into the third.
  • Score * and all combinators as zero.
  • Ignore :not(), :is() and :has() themselves but count the selectors inside them, and never count characters inside an attribute selector.

Notes

  • Specificity is compared lexicographically from left to right: [1, 0, 0] always beats [0, 10, 10]. A single #id can override a wall of classes.
  • Inline style="..." and !important sit above this triple in the real cascade. The score only decides which stylesheet rule wins before those overrides apply.

Stuck? The javascript course covers everything this challenge needs.

Try it yourself
Loading playground...
5 tests · +350 XP

Discussion

Sign in to post
Sign in to join the conversation.Sign in

No comments yet. Be the first to say something.