CSS Specificity Calculator
ReferenceCompute 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().
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#
- 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{. - Several selectors? Separate them with commas — they are ranked from most to least specific, and the winner is flagged.
- 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. - The coloured legend at the top right reminds you which dot maps to A / B / C.
- If your rule contained
!important, an informational note appears —!importantis 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:beforeand theof Sclause 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:
| Rank | Selector | Tuple |
|---|---|---|
| 1 | :is(.card, #hero) .button | (1, 1, 0) — winner |
| 2 | .button:hover | (0, 2, 0) |
| 3 | a.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.