Tools
Guides

CSS Specificity Calculator

Reference

Compute the specificity (A, B, C) of any CSS selector — IDs, classes, attributes, pseudo-classes and elements — compare several selectors at once, and see how each piece contributes. Handles :is(), :not() and :where().

100% client-side No backend
ID selectors (A) Classes, attributes & pseudo-classes (B) Types & pseudo-elements (C)
Selector Specificity (A, B, C)
Contribution
Enter one or more CSS selectors to compute their specificity.
On this page

What is CSS specificity?#

When two CSS rules both try to set the same property on the same element, the browser does not pick the one written last — it picks the one with the higher specificity. Specificity is a three-part score (A, B, C) that the cascade compares like a version number:

  • A — ID selectors (#header).
  • B — class selectors (.btn), attribute selectors ([type=email]), and pseudo-classes (:hover, :focus).
  • C — type selectors (div, a) and pseudo-elements (::before, ::first-line).

A is compared first; a tie falls through to B, then C. One ID (1,0,0) beats any number of classes (0,99,99), which in turn beats any number of types. The universal *, combinators (> + ~), and the nesting selector & add nothing.

This page computes that tuple for any selector you paste — including the modern functional pseudo-classes :is(), :where(), :not(), :has() and the of S clause of :nth-child() — ranks a list of selectors, and shows exactly which pieces contributed what. It is the fastest way to answer the universal CSS question: “why is my rule losing?”

How to use it#

  1. Click Sample to load a worked set of selectors, or clear the box and paste your own. You can enter a bare selector (#nav .item) or a whole rule (#nav .item { color: red }); the tool reads the part before {.
  2. Several selectors? Separate them with commas — they are ranked from most to least specific, and the winner is flagged.
  3. The right pane lists each selector with its (A, B, C) tuple and a coloured breakdown of every contributing piece (red = ID, blue = class/attribute/pseudo-class, green = type/pseudo-element), so you can see why the score is what it is.
  4. The coloured legend at the top right reminds you which dot maps to A / B / C.
  5. If your rule contained !important, an informational note appears — !important is a declaration-level override that sits above the specificity comparison, not inside it.

Key features#

  • Correct on the hard cases. :where() always contributes (0,0,0); :is() / :not() / :has() take the highest specificity among their arguments; legacy :before and the of S clause of :nth-child() are handled per Selectors Level 4.
  • Ranks a comma-separated list and flags ties, so you can see at a glance which of several rules will win.
  • Per-piece breakdown. Every #id, .class, [attr], :pseudo-class, ::pseudo-element, and type is itemised with the exact text it contributed.
  • Tolerant of full rules. Paste a:hover { text-decoration: underline } — comments are stripped and the declaration block is ignored automatically.
  • 100% local. Nothing leaves the page; no stylesheet is fetched or evaluated.

Worked example#

A common debugging puzzle: three rules all target the same link, and the wrong colour wins. Paste them into the box:

.button:hover
:is(.card, #hero) .button
a.button

The output ranks them:

RankSelectorTuple
1:is(.card, #hero) .button(1, 1, 0) — winner
2.button:hover(0, 2, 0)
3a.button(0, 1, 1)

The winner is not the one with the most classes. :is(.card, #hero) inherits the highest specificity of its arguments — #hero is an ID, so :is() carries (1,0,0) even though .card would only be (0,1,0). Adding .button bumps B by one, giving (1,1,0). That single ID in column A beats the two classes of .button:hover (0,2,0) — which is the exact surprise that makes :is() dangerous in production CSS.

If you needed the same grouping without raising specificity, you would swap :is() for :where():

:where(.card, #hero) .button   →   (0, 1, 0)

Now it ties with a.button minus the type, and source order decides. That is the whole reason :where() exists.

FAQ#

Why does one ID beat hundreds of classes?#

Specificity compares columns left to right like a version number, not as a summed total. (1,0,0) is “ahead” of (0,999,999) because column A is non-zero in the first and zero in the second; columns B and C are never even consulted. This is deliberate — IDs were meant to be unambiguous singletons.

What is the difference between :is() and :where()?#

They are identical in what they match, but opposite in specificity. :is(.a, #b) takes the highest specificity among its arguments (so #b makes it (1,0,0)); :where(.a, #b) always contributes (0,0,0) regardless of its arguments. Use :where() for low-specificity defaults you can easily override, and :is() when you genuinely want the combined selector to compete.

Does an inline style="…" attribute beat these?#

Yes. Inline styles outrank any selector-based rule in the normal cascade (they sit at a higher origin), so no selector tuple will override an inline style without !important. This is a separate axis from the (A,B,C) score, which is why the tool evaluates selectors only.

Where does !important fit in?#

!important is a declaration-level flag, not a selector property — it does not change the (A,B,C) tuple. It moves the whole declaration to a higher cascade origin, so an !important rule beats a normal rule regardless of specificity. The tool notes its presence informatively but keeps it out of the score.