CSS Specificity Calculator
Compute CSS specificity for any selector. Color-coded breakdown, multi-selector comparison, handles :is, :not, :has, :where per the W3C spec.
| Component | Examples | Weight |
|---|---|---|
| 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-elements | div, 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
