Specificity & Rules Conflict

Specificity is the score the browser gives every CSS selector to decide which rule wins when two rules target the same element and set the same property. Understanding it ends most CSS debugging mysteries.

The Specificity Score

Every selector gets a three-column score written as (A, B, C):

Column What counts Example
A — IDs Each #id in the selector #header → (1,0,0)
B — Classes Classes .foo, attributes [type], pseudo-classes :hover .card:hover → (0,2,0)
C — Elements Tag names p, div, pseudo-elements ::before ul li → (0,0,2)

Compare scores left to right. The first column where one selector beats the other is the winner. Column A always beats column B, column B always beats column C — no matter how large the numbers get.

🏅 The Medal Analogy

Think of specificity like medals: gold (IDs), silver (classes), bronze (elements). One gold medal beats a hundred silver medals. Ten silver medals beat a thousand bronze medals. The count within each medal type only matters when two selectors have the same number of higher-ranked medals.

Calculating Specificity Step by Step

/* Selector              A  B  C */
p                     /* 0, 0, 1 */
.warning              /* 0, 1, 0 */
p.warning             /* 0, 1, 1 */
#sidebar              /* 1, 0, 0 */
#sidebar p            /* 1, 0, 1 */
#sidebar .note        /* 1, 1, 0 */
#sidebar .note p      /* 1, 1, 1 */
nav ul li a:hover     /* 0, 1, 4 */

To find the winner between two selectors, start at column A. If they're equal, move to B. If still equal, move to C. If all three are equal, source order decides (last rule wins).

Common Conflicts and Who Wins

/* Conflict 1: element vs class */
p { color: grey; }       /* (0,0,1) */
.intro { color: blue; }  /* (0,1,0) — wins */
/* Conflict 2: class vs ID */
.hero { color: blue; }    /* (0,1,0) */
#hero { color: orange; }  /* (1,0,0) — wins */
/* Conflict 3: two classes vs one class + one element */
.card.featured { color: gold; }  /* (0,2,0) — wins */
.card p        { color: grey; }  /* (0,1,1) */
/* Conflict 4: many elements vs one class */
html body main section article p { color: red; }  /* (0,0,6) */
.intro                           { color: blue; } /* (0,1,0) — wins */
/* One class ALWAYS beats any number of elements */

The Universal Selector and Combinators

The universal selector * and combinators (>, +, ~, space) contribute zero specificity.

* { color: red; }         /* (0,0,0) — zero specificity */
* > p { color: blue; }    /* (0,0,1) — only the p counts */
div + p { color: green; } /* (0,0,2) — div and p count, + doesn't */

Inline Styles Beat Everything

An inline style attribute has a specificity of (1,0,0,0) — a fourth column that beats any selector, no matter how specific.

#page #sidebar .card p { color: teal; } /* (2,1,1) — very high */
<!-- inline style still wins over any selector -->
<p style="color: red;">Always red.</p>

!important Overrides Specificity

!important sits above the normal specificity system entirely. Even a low-specificity rule with !important beats a high-specificity rule without it.

#sidebar .note p { color: teal; }     /* (1,1,1) — high specificity */
p { color: red !important; }          /* (0,0,1) + !important — wins */

When two !important rules conflict, specificity decides between them normally. This is one reason chains of !important declarations become impossible to manage.

:is(), :not(), :has() and Specificity

Modern pseudo-classes take the specificity of their most specific argument:

/* :is() uses the specificity of its most specific argument */
:is(#sidebar, .card) p { color: blue; }
/* (1,0,1) — because #sidebar is the most specific argument */

/* :not() uses the specificity of its argument too */
p:not(.special) { color: grey; }
/* (0,1,1) */

/* :where() always contributes zero specificity — great for resets */
:where(h1, h2, h3) { margin: 0; }
/* (0,0,0) */
📌 :where() is your friend

Use :where() when you want to apply base styles that are easy to override. Because its specificity is always zero, any class or element rule will beat it without needing !important.

Practical Rules for Avoiding Conflicts

  1. Keep specificity low. Prefer classes over IDs for styling. Avoid chaining many selectors together.
  2. Never use IDs for styling. Save them for JavaScript and anchor links.
  3. Avoid inline styles. They create specificity that's nearly impossible to override without !important.
  4. Never use !important unless overriding third-party styles.
  5. Use DevTools. The Styles panel shows crossed-out rules that lost the specificity battle.
✅ Tip — Specificity Calculator

The site specificity.keegan.st lets you paste any selector and see its exact specificity score broken down column by column. Bookmark it.

Interview Questions

What is specificity in CSS?

A scoring system the browser uses to decide which CSS rule wins when multiple rules target the same element and property. It's scored as three columns (A, B, C) for IDs, classes, and elements respectively. Higher columns always beat lower ones regardless of number.

Does one ID always beat any number of classes?

Yes. One ID (A column) always beats any number of classes (B column). 100 classes (0,100,0) still loses to one ID (1,0,0) because column A is compared first.

What is the specificity of an inline style?

Inline styles have a (1,0,0,0) specificity — a fourth column that beats any selector in a stylesheet, no matter how many IDs it contains. Only !important can override it.

🏋️ Exercise

Calculate the specificity score for each selector, then rank them highest to lowest:

a) nav ul li a
b) .menu .item a
c) #nav .item
d) body #nav li.active a:hover
e) :is(.card, #box) p
See answers

a) nav ul li a → (0,0,4)

b) .menu .item a → (0,2,1)

c) #nav .item → (1,1,0)

d) body #nav li.active a:hover → (1,2,3)

e) :is(.card, #box) p → (1,0,1) — #box is most specific argument

Ranking: d > c > e > b > a

Summary

  • Specificity is scored as (A, B, C) — IDs, Classes/attributes/pseudo-classes, Elements/pseudo-elements.
  • Column A always beats column B, which always beats column C — regardless of count.
  • When specificity is equal, source order decides — the last rule wins.
  • Inline styles have a fourth column (1,0,0,0) and beat all selector-based rules.
  • !important breaks out of the normal specificity system entirely — avoid it in your own code.
  • :where() always has zero specificity — great for base styles you want to be easy to override.
  • Best practice: style with classes, keep selectors short, never use IDs for styling.

Related Topics