CSS Specificity Calculator

Work out the specificity of CSS selectors under Selectors Level 4, including :is(), :not(), :has(), :where() and :nth-child(of S), rank them to see which rule wins and why, and test them against your own HTML.

Calculator Web & Dev Updated Oct 4, 2026
Learn how this works
How to Use
  1. Type one selector per line, or paste a comma-separated list or whole CSS rules — the declarations are ignored and each selector is scored on its own.
  2. Read the ranking: every selector gets its (A, B, C) score, the winner is highlighted, and the line under it says which column decided it.
  3. Open Show Work to see each selector split into simple selectors, with the column each one counts in and what :is(), :not(), :has() or :where() took from their arguments.
  4. To check what a selector actually selects, paste an HTML snippet into the tester (or press Example HTML). The elements each selector matches are marked, and where two match the same element the winner is shown.
  5. Press Copy ranking for a plain-text list of scores, highest first.
Selectors
one per line, or comma lists
optional HTML snippet
Presets
Ranking, highest first

Tester matches

Selectors
—
Winner
—
Specificity
—
Decided by
—

Worked Example

One ID against three classes. #nav a is one ID (#nav, column A) and one type (a, column C): (1,0,1). .menu .item a.link is three classes (.menu, .item, .link, column B) and one type: (0,3,1). Column A is compared first and 1 > 0, so #nav a wins on the links both of them match, whatever order the rules are in.

No carrying between columns. html body div main section article div ul li p span is eleven type selectors, (0,0,11). A single .x is (0,1,0), and it wins: column B is checked before column C, and eleven in C never becomes one in B.

:is() against :where(). :is(#main, .content) p takes its most specific argument, #main (1,0,0), plus p: (1,0,1). :where(#main, .content) p is only (0,0,1), so a plain .content p at (0,1,1) overrides the :where() version but not the :is() one.

The common mistake: adding up every argument of :not(). a:not(.btn, #skip) is not (1,1,1). Like :is(), :not() counts only its most specific argument, #skip (1,0,0), so the selector is (0,0,1) + (1,0,0) = (1,0,1). The opposite mistake — thinking :not() adds nothing — is just as common, and an ID inside :not() makes the rule as hard to override as any ID selector.

Show Work

Enter a selector to see it broken into simple selectors.

Specificity Rules

Column A
#id → (1,0,0)
Each ID selector
Column B
.class  [attr]  :hover → (0,1,0)
Classes, attribute selectors, pseudo-classes
Column C
div  ::before → (0,0,1)
Type selectors and pseudo-elements, including :before written with one colon
Most specific argument
:is(X, Y) = :not(X, Y) = :has(X, Y) = max(X, Y)
Not the sum of the arguments
Zero
:where(…) = * = (0,0,0)
Combinators (space > + ~) also add nothing
Pseudo-class plus argument
:nth-child(An+B of S) = (0,1,0) + S
Also :host(S), :host-context(S), and ::slotted(S) as (0,0,1) + S
Compare
A, then B, then C, then source order
The first column that differs decides; later rule wins a tie

How Specificity Evolved

Specificity is as old as CSS. CSS level 1 (1996) counted IDs, classes and element names and said to concatenate the three numbers “in a number system with a large base”. That wording led to the long-running myth that 10 classes equal one ID; the columns were always meant to be compared separately, and some older engines that stored each column in 8 bits are the reason 256 chained classes once overflowed into the ID column. CSS 2.1 gave the style attribute its own, higher column, which is why inline styles beat any selector.

Selectors Level 3 (a Recommendation in 2011) let :not() take a simple selector and count its argument. Selectors Level 4 widened it to selector lists, added :is() and :has() with the “most specific argument” rule, and introduced :where() with zero specificity so that frameworks can ship defaults that never fight user styles. :is() and :where() reached Chrome 88, Firefox 78 and Safari 14 by early 2021; :has() followed in Safari 15.4 and Chrome 105 in 2022 and Firefox 121 in December 2023. Cascade layers (@layer, 2022) now let authors order whole groups of rules before specificity is even looked at.

About This Tool

This calculator parses each selector itself rather than relying on the browser, so it can show the work: every compound is split into simple selectors, each is placed in column A, B or C, and functional pseudo-classes show which argument they took. It follows Selectors Level 4, including :is(), :not() and :has() with selector lists, :where(), :nth-child(… of S), :host(), ::slotted(), escaped identifiers and namespaces. A nesting selector (&) is counted as zero and flagged, because its value depends on the parent rule.

The tester parses your HTML into a separate DOMParser document, which runs no scripts and loads nothing, and only reads it with querySelectorAll; the snippet is never put into this page. Nothing is sent anywhere. A list can be shared with ?s= followed by the selectors.

Related tools: HTML, CSS & JS Minifier, Beautifier & Linter, HTML Page Auditor, and Regex Tester.

Frequently Asked Questions

How is CSS specificity calculated?

Count three things. A is the number of ID selectors; B is classes, attribute selectors and pseudo-classes; C is type selectors and pseudo-elements. The universal selector * and the combinators (space, >, +, ~) count nothing. #nav .item a:hover has one ID (#nav), two in column B (.item and :hover) and one type (a), so it is (1,2,1).

Can enough classes ever beat an ID?

No. The columns are compared left to right and never carry over, so (1,0,0) beats (0,11,0) and (0,1,0) beats (0,0,11). One .x outranks html body div main section article div ul li p span, which is eleven type selectors. If two selectors tie in every column, the one that comes later in the stylesheet wins.

How do :is(), :not(), :has() and :where() count?

:is(), :not() and :has() take the specificity of their most specific argument, not the sum: a:not(.btn, #skip) is (0,0,1) + (1,0,0) = (1,0,1). :where() always counts zero, so :where(#main) p is just (0,0,1) — useful for defaults that are easy to override. :nth-child(2n+1 of .item) counts as a pseudo-class plus .item, so li:nth-child(2n+1 of .item) is (0,2,1).

Where do !important, inline styles and @layer fit in?

Specificity is only consulted after several earlier steps. The cascade first sorts by origin and importance (an !important declaration beats every normal one), then by whether the style is in a style="" attribute (which beats any selector), then by cascade layer (for normal rules, unlayered styles beat layered ones and later layers beat earlier ones). Only then does specificity decide, and source order breaks a final tie.

Why does the tester show no matches for ::before or :hover?

The snippet is parsed into a separate, inert document with DOMParser and is never added to this page, so nothing in it runs, loads or is hovered. Pseudo-elements such as ::before are not elements, so querySelectorAll never returns them, and state pseudo-classes such as :hover and :focus never match in a document nobody is using. Their specificity is still calculated correctly.

How do I use the CSS Specificity Calculator?

Just type your numbers. The answer shows up right away — there is no button to press. Change anything and it updates by itself.

Is it free? Does it work without internet?

Yes to both. It is free with no sign-up, and once the page has loaded it keeps working even with no internet.

Where does my data go?

Nowhere — every calculation runs on your own device. Nothing you enter is uploaded, logged, or stored.

Common Use Cases

Debugging a rule that will not apply

Paste the rule you wrote and the one beating it: .btn.primary at (0,2,0) loses to #header .btn at (1,1,0), and the ranking says it is the ID.

Writing overridable library CSS

Wrap defaults in :where() so .card a is (0,1,1) but :where(.card) a is (0,0,1), and any user rule wins.

Refactoring away IDs

Compare #sidebar .link (1,1,0) with [id="sidebar"] .link (0,2,0) before swapping, to see what else it now loses to.

Checking :has() and :not() lists

a:not(.btn, #skip) is (1,0,1), not (1,1,1): only the most specific argument counts.

Code review

Paste a whole stylesheet’s selectors to find the over-specific ones — anything above (0,3,0) is often a sign of a fight.

Last updated: