Dev Encyclopedia
ArticlesToolsContactAbout

Get notified when new content drops

No spam. Just new articles, tools, and updates straight to your inbox.

Dev Encyclopedia

A reference for builders

Dev.to
Discord
WhatsApp Channel
daily.dev
Hashnode
X

Content

  • Articles
  • Tools
  • About
  • Contact

Connect

  • support@devencyclopedia.com
  • RSS Feed

Legal

  • Privacy Policy
  • Terms of Service
  • Disclaimer

© 2026 Dev Encyclopedia

Back to top ↑
  1. Home
  2. /
  3. Tools
  4. /
  5. CSS Specificity Calculator
Free · Instant · No account

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.

  1. 1

    Paste your selector

    Type or pick a preset example to start

  2. 2

    See the score instantly

    ID, class, and type counts update as you type

  3. 3

    Compare to find the winner

    Switch to compare mode for two conflicting selectors

Zeeshan Tofiq

Zeeshan Tofiq

Full Stack Developer

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. 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. 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. 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. 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. 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. 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 three score boxesIDs, Classes, Types

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 read
The specificity level badgeLow, Moderate, or High

A 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)  High
The token breakdownPer-part contribution

Every 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)
The plain-English summary and compare verdictSentence form

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 typeExampleScoreColumn
Inline stylestyle="color:red"(1, 0, 0)a (highest)
ID#header(1, 0, 0)a
Class.button(0, 1, 0)b
Attribute[type="text"](0, 1, 0)b
Pseudo-class:hover, :nth-child(2)(0, 1, 0)b
Type selectordiv, p, a(0, 0, 1)c
Pseudo-element::before, ::after(0, 0, 1)c
Universal*(0, 0, 0)none
:where():where(.nav)(0, 0, 0)none (zero specificity)
:not() / :is() / :has():not(.active)argument's scoreb or c
!importantcolor: red !importantOverrides cascadenone (not a score)

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 .class

IDs 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 creep

You'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 overrides

Wrap 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 resets

Use :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.

SituationModeWhat to paste
A style is not applying and you do not know whyCompare twoYour selector in A, the selector DevTools shows as winning in B.
Reviewing a pull request that adds CSSSingle selectorEach new selector from the diff, to catch anything landing in the high band.
Deciding whether to reach for !importantCompare twoYour override in A, the rule you are fighting in B. If A already wins, the real problem is source order or a layer.
Overriding a third-party component libraryCompare twoYour override in A, the library selector copied from DevTools in B.
Writing a design system resetSingle selectorThe :where() wrapped version, to confirm it scores (0, 0, 0) and stays overridable.
Two rules keep flipping unpredictablyCompare twoBoth selectors. A tie verdict means specificity is not the tiebreaker and source order decides.

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.

ColumnWhat countsExampleScore
aID selectors#header(1, 0, 0)
bClass, attribute, pseudo-class.btn, [type], :hover(0, 1, 0)
cType selectors, pseudo-elementsdiv, 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.

css
/* 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) */

💡 Tip

Combinators (>, +, ~, whitespace) contribute nothing to specificity: only selector tokens count.

Does !important affect specificity?

!important does not change the specificity score: it bypasses the cascade entirely, regardless of score.

  • A rule marked !important wins over any non-!important rule, even one with a higher score.
  • Two competing !important rules fall back to comparing specificity normally.
  • Avoid !important in application CSS: it can only be overridden by more !important rules, 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.

css
/* Zero specificity: any single class can override */
:where(h1, h2, h3) { margin: 0; }

/* This wins without !important */
.custom-title { margin: 1.5rem; }

💡 Tip

Design systems and CSS resets use :where() to define defaults that any single class can override, with no specificity fights needed.

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 A10
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.

Related reading

Tool

CSS :has() Playground

Test CSS :has() selectors on real HTML components: forms, cards, navs, and more. See which elements match live in your browser.

Guide

Practical CSS :has() Patterns

How :has() takes its specificity from its most specific argument, and the patterns that keep that number low.

Guide

ARIA in React: Stop Using aria-label Wrong

Semantic HTML reduces your CSS specificity burden: correct element choices mean fewer override battles.

Zeeshan Tofiq

Zeeshan Tofiq

Full Stack Developer

Full stack developer with over 6 years of experience building production applications. Writes practical guides on JavaScript, TypeScript, React, Node.js, and cloud infrastructure. Focused on helping developers solve real problems with clean, maintainable code.

Enjoyed this article?

Get practical dev guides, tool updates, and new articles delivered straight to your inbox. No spam, unsubscribe anytime.