Figure out CSS specificity in seconds.
Paste any CSS selector and see its (a, b, c) score, a token-by-token breakdown of every contributing part, and a plain-English explanation. Compare two selectors to find out which one wins and why.
Paste your selector
Type or pick a preset example to start
See the score instantly
ID, class, and type counts update as you type
Compare to find the winner
Switch to compare mode for two conflicting selectors
How the specificity calculator works
The calculator implements the same counting rules a browser applies when two rules target the same element. Here is the full path from the selector you type to the score you see.
- 1
Paste a selector or pick one of the presets
Anything you would write to the left of an opening brace works: descendant chains, child combinators, attribute selectors, pseudo-classes, pseudo-elements, and functional selectors such as :not() and :where(). The preset chips load worked examples if you want to see the rules in action first.
- 2
The selector is scanned left to right and split into tokens
The parser walks the string one character at a time and cuts it into individual selector parts. Whitespace and the combinators (child, adjacent sibling, general sibling) are recognised and skipped, because combinators contribute nothing to specificity.
- 3
Every token is assigned to column a, b, or c
ID selectors go to column a. Classes, attribute selectors, and ordinary pseudo-classes go to column b. Type selectors and pseudo-elements go to column c. The universal selector is recognised but scores zero in every column.
- 4
Functional pseudo-classes are resolved against their arguments
Anything inside :where() is parsed and then discarded, so :where() always contributes zero. For :is(), :not(), and :has(), the arguments are parsed individually and the single most specific one is taken as the contribution, which is exactly what the CSS specification requires.
- 5
The token contributions are summed into one (a, b, c) score
Each column is added up independently. Columns never carry into each other, so ten classes stay at (0, 10, 0) rather than rolling over into column a. The result is also classified as low, moderate, or high so you can judge it at a glance.
- 6
Compare mode scores two selectors and names the winner
Switch to 'Compare two' and both selectors are parsed the same way, then compared column by column: a first, then b, then c. The first column that differs decides the winner, and a sentence underneath explains which column made the call.
What the results mean
The calculator returns four pieces of output. The score tells you what the browser will do, and the breakdown tells you which part of your selector is responsible.
The headline (a, b, c) score, one box per column. Compare two scores left to right: the first column where they differ decides everything, and the remaining columns are irrelevant. A box lights up only when its count is above zero.
#header .nav a
IDs: 1 Classes: 1 Types: 1 → (1, 1, 1)
.site .nav .menu .link a
IDs: 0 Classes: 4 Types: 1 → (0, 4, 1)
# (1, 1, 1) wins: column a decides, column b is never readA maintainability signal rather than a cascade rule. Any ID pushes a selector straight to high. Three or more column-b tokens, or two column-b tokens alongside two column-c tokens, also count as high. Two column-b tokens or three type selectors is moderate, and anything below that is low.
.btn → (0, 1, 0) Low
.card .btn → (0, 2, 0) Moderate
.card .btn:hover → (0, 3, 0) High
#app .btn → (1, 1, 0) HighEvery token appears as a chip showing the text, its type, and the exact amount it added in +a,b,c form. Colour groups the chips by type, so a violet chip is the ID that is inflating your score. Combinators never appear here because they contribute nothing.
nav > ul li.active:hover
nav Type +0,0,1
ul Type +0,0,1
li Type +0,0,1
.active Class +0,1,0
:hover Pseudo-class +0,1,0
Total (0, 2, 3)In single mode you get one sentence counting what the selector contains. A selector that scores zero in all three columns is called out explicitly, because it can never win a conflict on specificity alone and depends entirely on source order. In compare mode the winning card is highlighted and a sentence names the deciding column, or reports a tie.
:where(.nav) a
→ 1 type selector or pseudo-element (0, 0, 1)
The :where() chip contributes +0,0,0
Compare: #header .nav vs .header .nav.active
→ The first selector wins: it has 1 more ID.
IDs always beat any number of classes.Specificity quick reference
| Selector type | Example | Score |
|---|---|---|
| Inline style | style="color:red" | (1, 0, 0) |
| ID | #header | (1, 0, 0) |
| Class | .button | (0, 1, 0) |
| Attribute | [type="text"] | (0, 1, 0) |
| Pseudo-class | :hover, :nth-child(2) | (0, 1, 0) |
| Type selector | div, p, a | (0, 0, 1) |
| Pseudo-element | ::before, ::after | (0, 0, 1) |
| Universal | * | (0, 0, 0) |
| :where() | :where(.nav) | (0, 0, 0) |
| :not() / :is() / :has() | :not(.active) | argument's score |
| !important | color: red !important | Overrides cascade |
Combinators > + ~ and whitespace contribute zero specificity and are ignored in the calculation.
Common specificity problems and fixes
My class won't override a component's ID style
#id beats .classIDs always beat classes: no number of classes can win. Refactor: remove the ID selector from your stylesheet and use a class instead. Or wrap the component in :where(#id) to strip the ID's specificity to zero.
I'm using !important everywhere and it's escalating
Specificity creepYou're in a specificity war. Use CSS @layer to establish a clear priority order: wrap library styles in one layer and your overrides in another. Your layer always wins without any !important.
I can't override a third-party library's styles
Library overridesWrap the library import in a @layer: @layer library { @import 'lib.css'; }, then write your overrides outside any layer. Un-layered CSS always beats layered CSS.
I need base styles that are always easy to override
Design system resetsUse :where() to write base styles at zero specificity: :where(h1, h2, h3) { margin: 0; }. Any single class override will beat it without needing !important.
When to use the specificity calculator
Six real situations, with the mode to pick and exactly what to put in each input.
| Situation | Mode |
|---|---|
| A style is not applying and you do not know why | Compare two |
| Reviewing a pull request that adds CSS | Single selector |
| Deciding whether to reach for !important | Compare two |
| Overriding a third-party component library | Compare two |
| Writing a design system reset | Single selector |
| Two rules keep flipping unpredictably | Compare two |
Frequently Asked Questions
What is CSS specificity?
CSS specificity is a three-number score (a, b, c) that determines which CSS rule wins when two rules target the same element. Selectors are compared left to right: column A always beats column B.
| Column | What counts | Example | Score |
|---|---|---|---|
| a | ID selectors | #header | (1, 0, 0) |
| b | Class, attribute, pseudo-class | .btn, [type], :hover | (0, 1, 0) |
| c | Type selectors, pseudo-elements | div, p, ::before | (0, 0, 1) |
How do I calculate CSS specificity by hand?
Count each part separately: IDs → column A, classes/attributes/pseudo-classes → column B, types/pseudo-elements → column C.
/* nav > ul li.active:hover (step by step) */
nav /* type → (0, 0, 1) */
ul /* type → (0, 0, 1) */
li /* type → (0, 0, 1) */
.active /* class → (0, 1, 0) */
:hover /* pseudo → (0, 1, 0) */
/* > + ~ space (combinators ignored) */
/* Total: (0, 2, 3) */Does !important affect specificity?
!important does not change the specificity score: it bypasses the cascade entirely, regardless of score.
- A rule marked
!importantwins over any non-!importantrule, even one with a higher score. - Two competing
!importantrules fall back to comparing specificity normally. - Avoid
!importantin application CSS: it can only be overridden by more!importantrules, causing escalating stylesheet debt.
What is :where() and why does it have zero specificity?
:where() applies styles based on its argument selector but contributes zero specificity ((0, 0, 0)), regardless of how complex the argument is.
/* Zero specificity: any single class can override */
:where(h1, h2, h3) { margin: 0; }
/* This wins without !important */
.custom-title { margin: 1.5rem; }Can 100 class selectors ever beat 1 ID selector?
No. The columns (a, b, c) are compared left to right and never overflow into each other.
| #header (1 ID) | .a.b.c × 100 classes | |
|---|---|---|
| Score | (1, 0, 0) | (0, 100, 0) |
| Column A | 1 | 0 |
| Winner? | Yes (column A decides) | No (loses on column A) |
To avoid this trap: don't use ID selectors in stylesheets. Use classes and :where() instead.