CSS Specificity Calculator
Calculate CSS specificity as (a,b,c) with breakdown by selector
Understanding CSS Specificity
CSS specificity is the algorithm browsers use to determine which CSS declaration applies when multiple rules target the same element. Every selector in your stylesheet carries a specificity weight, and when two or more declarations compete for the same property on the same element, the one with the higher specificity wins — regardless of source order. Understanding this mechanism is essential for writing predictable, maintainable stylesheets and avoiding the frustrating debugging sessions that arise from unexpected style overrides.
The specificity calculation follows a three-component scoring system defined in the W3C Selectors
specification. Each selector is reduced to a tuple (a, b, c) where a counts
ID selectors, b counts class selectors, attribute selectors, and pseudo-classes, and
c counts type (element) selectors and pseudo-elements. This tool parses any CSS selector
and computes its specificity score instantly, helping you resolve cascade conflicts without trial and error.
The Specificity Scoring System: (a, b, c)
Specificity is not a single number — it is a three-component tuple compared from left to right.
A selector with specificity (1, 0, 0) always beats (0, 15, 15) because
the first component (ID selectors) takes absolute precedence over the second and third. This means
a single ID selector outweighs any number of classes or elements combined.
The components are calculated as follows: Component a increments by 1 for each ID
selector (e.g., #header). Component b increments by 1 for each class
selector (.nav), attribute selector ([type="text"]), or pseudo-class
(:hover, :nth-child(2), :focus). Component c
increments by 1 for each type selector (div, p, h1) or
pseudo-element (::before, ::after, ::placeholder).
The universal selector (*), combinators (>, +,
~, space), and the negation pseudo-class :not() itself do not contribute
to specificity — though the arguments inside :not() do count. For example,
:not(.active) adds (0, 1, 0) from the .active argument.
Inline Styles, !important, and the Full Cascade
Beyond selector specificity, the CSS cascade has additional layers. Inline styles applied via the
style attribute on an element override all selector-based specificity. They effectively
have a specificity of (1, 0, 0, 0) in a four-component model — higher than any
selector written in a stylesheet. The only way to override an inline style from a stylesheet is
with !important.
The !important annotation elevates a declaration above all normal declarations,
including inline styles. However, when multiple !important declarations compete, normal
specificity rules apply among them. This creates a dangerous escalation pattern often called
"specificity wars" — once you start using !important to override styles, you often
need more !important rules to override those overrides, leading to unmaintainable CSS.
The full cascade priority (from lowest to highest) is: user agent styles, normal author styles
(ordered by specificity), !important author styles, !important user
agent styles. Within each origin, specificity determines the winner, and among equal specificity,
source order (last declaration wins) is the tiebreaker.
Modern CSS Features: :is(), :where(), :has(), and @layer
Modern CSS introduces pseudo-classes with special specificity behaviors. The :is()
pseudo-class (formerly :matches()) takes the specificity of its most specific argument.
For example, :is(#id, .class, div) has specificity (1, 0, 0) because
#id is the highest-specificity argument. This makes :is() useful for
grouping selectors without writing them out individually, but you must be aware that even unused
arguments affect the specificity of the entire selector.
In contrast, :where() has zero specificity regardless of its arguments. This makes it
ideal for writing base styles or resets that are easy to override. A selector like
:where(#id, .class) p has specificity (0, 0, 1) — only the p
type selector contributes. This is a powerful tool for library authors who want their defaults to
be easily overridable by consumers.
The :has() relational pseudo-class takes the specificity of its most specific argument,
similar to :is(). So div:has(.active) has specificity (0, 1, 1)
— one class from .active and one type from div. Finally, CSS Cascade
Layers (@layer) introduce an entirely new dimension to the cascade. Layers are ordered
by declaration order, and styles in later layers override earlier layers regardless of specificity.
Unlayered styles always beat layered styles. This gives developers explicit control over cascade
priority without resorting to specificity hacks or !important.
Common Specificity Problems and Debugging Strategies
The most frequent specificity problem is unintended overrides: you write a rule expecting it to apply, but a more specific selector elsewhere wins. Debugging starts with browser DevTools — the Styles panel shows all matching declarations with strikethrough on overridden ones, and hovering reveals the specificity score. If you see your rule crossed out, compare its specificity tuple against the winning selector.
Common anti-patterns include: over-qualifying selectors (e.g., div#header ul.nav li a.link
when .nav-link suffices), using IDs for styling (which creates high-specificity
anchors that are hard to override), nesting selectors too deeply in preprocessors like Sass, and
scattering !important throughout the codebase. Each of these makes future changes
harder because new rules must match or exceed the existing specificity.
Best practices for managing specificity include: using a flat class-based methodology like BEM
(Block Element Modifier) which keeps specificity uniformly at (0, 1, 0), reserving
IDs for JavaScript hooks rather than styling, leveraging :where() for overridable
defaults, and adopting @layer to establish clear cascade boundaries between resets,
base styles, components, and utilities.
Code Examples
Comparing specificity scores for common selectors
/* Specificity: (0, 0, 1) — one type selector */
p { color: black; }
/* Specificity: (0, 1, 0) — one class selector */
.highlight { color: blue; }
/* Specificity: (0, 1, 1) — one class + one type */
p.highlight { color: green; }
/* Specificity: (1, 0, 0) — one ID selector */
#main { color: red; }
/* Specificity: (1, 1, 1) — one ID + one class + one type */
div#main .content p { color: purple; }
/* Specificity: (0, 2, 1) — two classes + one type */
ul.nav li.active { color: orange; }
/* :where() contributes zero specificity */
/* Specificity: (0, 0, 1) — only the p counts */
:where(.article, #post) p { color: gray; }
/* :is() takes highest argument specificity */
/* Specificity: (1, 0, 1) — #post is highest + p type */
:is(.article, #post) p { color: teal; }
/* :has() takes argument specificity */
/* Specificity: (0, 1, 1) — .active class + div type */
div:has(.active) { border: 1px solid gold; }Output:
Selector | Specificity
--------------------------------|------------
p | (0, 0, 1)
.highlight | (0, 1, 0)
p.highlight | (0, 1, 1)
#main | (1, 0, 0)
div#main .content p | (1, 1, 3)
ul.nav li.active | (0, 2, 2)
:where(.article, #post) p | (0, 0, 1)
:is(.article, #post) p | (1, 0, 1)
div:has(.active) | (0, 1, 1)Standards & Specifications
- W3C Selectors Level 4 — The current W3C specification defining selector specificity calculation rules, including :is(), :where(), and :has() behaviors
- W3C CSS Cascading and Inheritance Level 5 — Defines the full cascade algorithm including origins, importance, layers, and specificity ordering
FAQ
Does it support `:is()` and grouped selectors?
Yes, grouped selectors are analyzed individually and the highest score is shown as global score.