HTML & CSS6 min read

CSS Specificity — How It Actually Works

Finally understand CSS specificity. Learn the specificity hierarchy, how to calculate scores, when to use !important, and practical debugging tips.

cssspecificityselectorsdebugging

CSS Specificity — How It Actually Works

"Why isn't my CSS working?" — If you've ever asked this, the answer is almost always specificity. Understanding specificity means you'll spend less time fighting CSS and more time building.

What is Specificity?

Specificity is how the browser decides which CSS rule wins when multiple rules target the same element. It's a scoring system.

Think of it like a tiebreaker. When two rules conflict:

.card { color: blue; }
div.card { color: red; }

The element is red. div.card has higher specificity than .card.

The Specificity Hierarchy

From lowest to highest priority:

LevelSelector TypeExample
0Universal, combinators*, >, +, ~
1Element, pseudo-elementsdiv, p, ::before
2Class, attribute, pseudo-class.card, [type="text"], :hover
3ID#header
Inline stylesstyle="color: red"
∞+!importantcolor: red !important

Calculating Specificity

Think of specificity as a three-digit score: (IDs, Classes, Elements)

/* (0, 0, 1) — one element */
p { }

/* (0, 1, 0) — one class */
.card { }

/* (0, 1, 1) — one class + one element */
div.card { }

/* (1, 0, 0) — one ID */
#header { }

/* (1, 1, 1) — one ID + one class + one element */
#header .nav a { }

/* (0, 2, 1) — two classes + one element */
.sidebar .nav-link:hover { }

Comparison Rules

  1. Compare IDs first. Higher ID count wins.
  2. If IDs are tied, compare classes.
  3. If classes are tied, compare elements.
#header .nav { }       /* (1, 1, 0) ← wins */
.header .nav .link { } /* (0, 3, 0) — 3 classes loses to 1 ID */

IDs always beat classes, no matter how many classes you stack.

Common Specificity Battles

Overriding Framework Styles

When Bootstrap or a UI library sets styles, they often use specific selectors:

/* Bootstrap uses: */
.btn.btn-primary { color: #fff; } /* (0, 2, 0) */

/* Your override needs at least: */
.my-button.btn.btn-primary { color: #000; } /* (0, 3, 0) */

Utility Class Losing

.text-red { color: red; }       /* (0, 1, 0) */
.card .title { color: black; }  /* (0, 2, 0) ← wins */

Your utility class loses because the other rule has higher specificity.

Rules to Live By

1. Avoid IDs for Styling

IDs have nuclear-level specificity. Once you use #header for styling, you need another ID to override it. Use classes instead.

2. Avoid !important

!important breaks the cascade. It's a sledgehammer. The only way to override !important is with another !important that has equal or higher specificity. This creates an arms race.

When !important is okay:

  • Overriding third-party library styles you can't modify
  • Utility classes that absolutely must apply (e.g., .hidden { display: none !important })

3. Keep Specificity Flat

Better:

.card-title { font-size: 1.25rem; }

Worse:

.sidebar .card .card-header .card-title { font-size: 1.25rem; }

The flatter your selectors, the easier they are to override and maintain.

4. Use the "Source Order" Tiebreaker

When specificity is equal, the last rule wins:

.text { color: blue; }
.text { color: red; }  /* ← wins (same specificity, comes later) */

This is why putting your custom CSS after a framework's CSS works.

Debugging Specificity

Browser DevTools

Open DevTools → select an element → look at the Styles panel. Crossed-out properties are being overridden by higher-specificity rules. DevTools shows you exactly which rule is winning and why.

The "Add a Class" Fix

If your style isn't applying, add another class to increase specificity:

/* Not working: */
.title { color: blue; }

/* Fix — make it more specific: */
.card .title { color: blue; }

Specificity and CSS Layers

CSS Cascade Layers (@layer) are a modern way to manage specificity at scale:

@layer base, components, utilities;

@layer base {
  a { color: blue; }
}

@layer utilities {
  .text-red { color: red; }  /* wins even with lower specificity */
}

Layers let you control which group of styles takes priority, regardless of specificity within each layer.

Key Takeaways

  1. Specificity score: count (IDs, Classes, Elements) — IDs always win over classes
  2. Avoid IDs and !important for styling — they cause specificity wars
  3. Keep selectors flat — .card-title beats .sidebar .card .header .title in maintainability
  4. When specificity is equal, last rule in source order wins
  5. Use DevTools to debug which rule is winning

🚀 Practice What You Learned

Apply these concepts with hands-on coding challenges: