Calculate any selector score, run live battles, and master modern :is() / :where() / @layer rules.
Type any CSS selector to instantly see its (ID, Class, Element) score broken down token by token.
(1,0,0) always beats (0,99,99) because the first column wins before
the others are even looked at. You cannot "overflow" column B into column A no matter how many classes you stack.
Paste two selectors targeting the same element. See the scores, see the winner, and understand exactly why.
The specificity rules for modern CSS selectors are different from what most tutorials teach. And @layer bypasses specificity entirely.
:is() takes the specificity of its most specific argument, not an average. This surprises developers who expect it to add nothing.
:where() was built to be overridable. It contributes 0 to specificity regardless of what's inside — making it the perfect escape hatch for design systems.
:not() itself adds nothing — but its argument's specificity is counted. This means :not(#id) adds (1,0,0) to the score.
Like :not(), :has() contributes the specificity of its most specific argument. The parent selector penalty is zero.
Cascade layers resolve before specificity. A low-specificity rule in a later layer always wins over a high-specificity rule in an earlier layer — no matter what.
Layer order is declared with @layer base, components, overrides; — last layer has highest priority.
This is how design systems escape specificity wars without writing !important.
!important doesn't change a rule's specificity score.
It moves the declaration into a separate cascade origin — the "important" origin — which is evaluated
before the regular author origin. Two !important declarations are then resolved by specificity,
but !important always beats non-important declarations regardless of score.
This is why the fix to !important is never more !important.