CSS Specificity Calculator

Compute CSS specificity for any selector. Color-coded breakdown, multi-selector comparison, handles :is, :not, :has, :where per the W3C spec.

CSS Selectors (one per line)
One selector per line. Comma-separated rules count as one input line.
Specificity Reference
ComponentExamplesWeight
a IDs#header, #main(1, 0, 0)
b Classes / Attributes / Pseudo-classes.btn, [type="text"], :hover, :nth-child(2)(0, 1, 0)
c Types / Pseudo-elementsdiv, p, ::before, ::placeholder(0, 0, 1)
*, :where(...), +, >, ~(0, 0, 0)
:is(A, B), :not(A), :has(A)max of argument

CSS Specificity Calculator — Parse Selectors, Compute (a, b, c)

Paste any CSS selector — simple or compound, with combinators, pseudo-classes, pseudo-elements, or modern :is/:not/:where/:has — and the calculator returns the W3C specificity triple. The breakdown shows which component each token contributes to, and a comparison view ranks multiple selectors so you can see at a glance which rule wins the cascade.

What is CSS specificity in one paragraph?

When two CSS rules try to set the same property on the same element, the browser needs a tiebreaker. The first tiebreaker is *origin and importance* (user-agent < author < user, then !important reverses that). After that, it's *specificity*: each selector gets a weight, expressed as three numbers — (a, b, c) — and the higher tuple wins, compared left-to-right.

The three slots count:

- **a** — the number of ID selectors (`#foo`)
- **b** — the number of class selectors (`.bar`), attribute selectors (`[type=text]`), and pseudo-classes (`:hover`)
- **c** — the number of type selectors (`div`) and pseudo-elements (`::before`)

A selector like `#nav .item a:hover` has one ID, two class/pseudo-class entries, and one type — specificity (1, 2, 1). It beats `.menu .item a:focus` (0, 3, 1) because the first slot wins regardless of how high the later slots are.

How do `:is()`, `:not()`, and `:has()` affect specificity?

All three contribute the *maximum* specificity of their arguments, not zero, not the sum.

Example:

```
:is(.btn, #primary) { color: red; }
```

The specificity is determined by `#primary` (1, 0, 0), not by `.btn` (0, 1, 0). The whole `:is(...)` token contributes (1, 0, 0).

The same rule applies to `:not(...)` and `:has(...)` — they take the heaviest selector inside the parentheses. This is why `:not(#admin)` is a heavy selector despite looking innocent; it's specificity (1, 0, 0) all on its own.

This behavior is defined in CSS Selectors Level 4. Older `:not()` (Level 3, single-simple-selector form) followed the same rule, so most browsers have always agreed.

What about `:where(...)`?

`:where(...)` is the escape hatch: it contributes **zero specificity** no matter what's inside.

```
:where(#header, .nav, h1) { color: blue; }
```

Specificity = (0, 0, 0). This is intentional. `:where()` lets you write a base style that grouped selectors share without locking out future overrides — anything that targets the same elements with even one class will win.

A common pattern is wrapping reset or default styles in `:where(...)` so they have effectively no specificity weight, while leaving authored component styles free to override them with normal selectors. This was added in CSS Selectors Level 4 (2018) and shipped in all evergreen browsers by 2021.

Why do I sometimes see `(a, b, c, d)` with four slots?

Older articles add a fourth slot at the start for **inline style** (the `style="..."` attribute), giving (1, 0, 0, 0) for any inline declaration. The W3C dropped this from the formal specificity tuple because inline style is a separate origin layer — it wins all author-level cascade decisions before specificity even gets compared.

In practice:

1. `!important` declarations win (origin layer).
2. Inline style beats any selector without `!important`.
3. Selectors are then ranked by (a, b, c) — IDs, classes/attrs/pseudo-classes, types/pseudo-elements.
4. If specificity ties, the rule declared last in source order wins.

This calculator follows the modern three-slot convention. If you're comparing against an older tutorial, mentally pad with a leading 0 — `(0, 1, 2, 1)` and `(1, 2, 1)` describe the same selector.

Does the universal selector `*` or combinators add specificity?

No. None of the following contribute to specificity:

- `*` (universal selector)
- ` ` (descendant combinator, written as whitespace)
- `>` (child combinator)
- `+` (adjacent sibling combinator)
- `~` (general sibling combinator)
- `||` (column combinator, for tables — Level 4)

A selector like `* > * + *` has specificity (0, 0, 0) — three universals and two combinators, all weightless. Combinators describe *relationships*, not what's being matched, so they don't add weight.

This is why `body > main p` (0, 0, 3) ties with `h1 ~ h2 ~ p` (0, 0, 3) — same three type selectors, different combinators, same specificity. The cascade then decides by source order.

What's the difference between pseudo-classes and pseudo-elements?

**Pseudo-classes** describe a state of an element:

- `:hover`, `:focus`, `:active`, `:checked`, `:disabled`
- `:nth-child(2)`, `:nth-of-type(odd)`, `:first-child`
- `:lang(en)`, `:dir(rtl)`, `:state(custom)`

They contribute to slot **b** — same weight as a class selector.

**Pseudo-elements** describe a virtual sub-part of an element:

- `::before`, `::after`, `::first-line`, `::first-letter`
- `::placeholder`, `::marker`, `::backdrop`, `::selection`

They contribute to slot **c** — same weight as a type selector.

The syntactic difference is a single colon (`:hover`) vs double colon (`::before`). CSS2 used a single colon for the four oldest pseudo-elements (`:before`, `:after`, `:first-line`, `:first-letter`) — this calculator accepts both forms and classifies those four as pseudo-elements either way.

How do I lower specificity when I'm stuck in a war with another stylesheet?

Several techniques in increasing severity:

1. **Use `:where()`** — wrap the heavy parts of your selector in `:where()` so they contribute nothing. `:where(.legacy) .button` → (0, 1, 0) instead of (0, 2, 0). Best for reset stylesheets.
2. **Drop IDs from selectors** — refactor `#nav .item` to `.nav .item` if the markup allows. IDs are the single heaviest contributor.
3. **Use a single class** — flat selectors like `.btn-primary` win because they tie at (0, 1, 0) against most authored styles, leaving source order to decide.
4. **Add a class duplicate** — `.btn.btn` (0, 2, 0) is a known hack to bump specificity up *without* needing IDs.
5. **Use cascade layers** — `@layer base, components, utilities;` lets you control which file wins regardless of specificity. The later-declared layer wins, full stop.
6. **Use `!important`** — last resort. It works but creates a brittle chain of override wars.

The modern playbook is: minimize specificity in component CSS (single classes), wrap legacy/reset rules in `:where()`, and use cascade layers for tooling/utility split.

Is this calculator private and offline?

Yes. Everything is computed locally in the browser by a few hundred lines of JavaScript:

- The selector parser is hand-written, no external CSS engine called.
- No telemetry, no analytics for the calculations themselves.
- The Examples button populates the textarea with built-in strings; nothing fetched.
- The page shares the site's normal assets (Bootstrap CSS, icons) but no third-party API is contacted for the actual specificity math.

You can verify by opening DevTools → Network and watching while you type or click Calculate — no request fires. This also means the calculator works offline once the page has loaded, which is handy if you're auditing a stylesheet on a flight or in a low-signal environment.

Key Features

  • W3C-correct (a, b, c) tuple for any CSS selector
  • Color-coded breakdown highlighting which token contributes to each slot
  • Multi-selector comparison view, sorted by specificity descending
  • Correct handling of :is(), :not(), :has() — max specificity of argument
  • :where(...) recognized as zero specificity
  • Pseudo-elements (::before, ::after, ::first-line) classified separately from pseudo-classes
  • Legacy CSS2 single-colon pseudo-elements (:before, :after) accepted
  • Universal selector * and combinators (>, +, ~, space) correctly weighted as zero
  • Attribute selectors, including [attr=value] and [attr^=value]
  • Functional pseudo-classes :nth-child(), :nth-of-type(), :lang(), :dir()
  • Examples preset loads 10 representative selectors for instant comparison
  • Tie detection — highlights selectors with equal specificity
  • Pure JavaScript, no external CSS engine
  • Works offline after first load
  • 100% client-side — your selectors stay in your browser