Advertisement
🔧 Advanced CSS

CSS Specificity – Why Your Styles Get Overridden

Specificity is the algorithm browsers use to decide which CSS rule wins when multiple rules target the same element and property. Understanding it means you stop fighting your own styles — and you know exactly why color: red isn't working and what to do about it. Specificity is one of the most common sources of confusion in CSS, and mastering it will make you dramatically faster at debugging.

⏱️ 20 min read 🎯 Intermediate 📅 Updated 2026 👁️ Lesson 4 of 5

How Specificity Works

Each CSS selector has a specificity score — a three-column value written as (a, b, c). The browser compares scores column by column, left to right. The rule with the highest score wins. If scores are equal, the rule declared last in the source wins (the cascade).

Category What counts Score contribution
Column A — ID selectors #id (1, 0, 0)
Column B — Class, attribute, pseudo-class .class, [attr], :hover, :nth-child() (0, 1, 0)
Column C — Element, pseudo-element div, p, a, ::before (0, 0, 1)
Inline style style="..." attribute (1, 0, 0, 0)
Universal / combinators *, >, +, ~,   (0, 0, 0)
CSS – Calculating specificity scores
/* Selector                         Score (a, b, c) */

p                                /* (0, 0, 1) — 1 element */
.intro                           /* (0, 1, 0) — 1 class */
#header                          /* (1, 0, 0) — 1 ID */
div p                            /* (0, 0, 2) — 2 elements */
.nav li                          /* (0, 1, 1) — 1 class, 1 element */
.nav li a                        /* (0, 1, 2) — 1 class, 2 elements */
#header .nav li a                /* (1, 1, 2) — 1 ID, 1 class, 2 elements */
.card:hover                      /* (0, 2, 0) — 1 class, 1 pseudo-class */
input[type="text"]               /* (0, 1, 1) — 1 attribute, 1 element */
ul li:first-child                /* (0, 1, 2) — 1 pseudo-class, 2 elements */
#nav > ul > li > a:hover         /* (1, 1, 3) — 1 ID, 1 pseudo-class, 3 elements */

Comparing Specificity

CSS – Specificity comparison examples
/* Which color wins? */
p { color: blue; }           /* (0,0,1) */
.text { color: red; }        /* (0,1,0) */
/* Winner: .text → red (0,1,0 beats 0,0,1) */

#main p { color: green; }    /* (1,0,1) */
.section .text { color: orange; } /* (0,2,0) */
/* Winner: #main p → green (1 in column A wins regardless of B and C) */

/* Same specificity — last one in source wins */
.btn { background: blue; }   /* (0,1,0) */
.cta { background: red; }    /* (0,1,0) */
/* <button class="btn cta"> → red wins (declared last) */
Advertisement

!important

!important overrides all specificity — including inline styles. It adds a separate "important" layer above the normal specificity scale. When two !important declarations compete, the one with higher specificity wins.

CSS – !important
/* !important beats everything in the normal cascade */
p { color: blue !important; }     /* wins over: */
#main p { color: red; }           /* even though (1,0,1) > (0,0,1) */

/* Two !important — specificity wins */
p { color: blue !important; }     /* (0,0,1) with !important */
.text { color: red !important; }  /* (0,1,0) with !important — wins */

/* Utility class pattern — legitimate use of !important */
.text-center { text-align: center !important; }
.hidden      { display: none !important; }
.mt-0        { margin-top: 0 !important; }

The Cascade Order

When specificity is equal, the cascade applies — rules are resolved in this order (later stages override earlier ones):

Text – Cascade order (lowest to highest)
1. Browser default stylesheet (user-agent styles)
2. User stylesheet (accessibility overrides in browser settings)
3. Author stylesheet (your CSS) — by order:
   a. External stylesheets (in link order)
   b. Internal <style> blocks (in document order)
   c. Inline styles (style="" attribute)
4. !important in author styles
5. !important in user styles (accessibility overrides — highest priority)

Inheritance

Some properties inherit down to children automatically (color, font-*, line-height). Others don't (border, padding, margin, background). You can force or reset inheritance with the keywords below.

CSS – Inheritance keywords
/* inherit — force the element to inherit its parent's value */
.child { color: inherit; }

/* initial — reset to the browser's initial value for that property */
button { background: initial; }   /* removes browser button background */

/* unset — inherit if naturally inherited, otherwise initial */
.reset { all: unset; }   /* strips all styles from an element */

/* revert — roll back to browser's user-agent stylesheet value */
h1 { font-size: revert; }

Avoiding Specificity Wars

Specificity wars happen when you pile on selectors trying to override each other. The solution is to keep specificity low and consistent.

CSS – Specificity best practices
/* BAD — high ID specificity locks you in */
#page #main-content .sidebar .widget h3 { color: red; }

/* GOOD — single class, low specificity */
.widget-title { color: red; }

/* BAD — escaping with !important */
.btn { color: blue !important; }

/* GOOD — raise specificity slightly instead */
.btn.btn-primary { color: #1572B6; }

/* Use :where() to write complex selectors without adding specificity */
:where(#app) .btn { color: #1572B6; }   /* (0,1,0) — not (1,1,0) */

/* Layers: @layer lets you control cascade order independent of specificity */
@layer base, components, utilities;

@layer base {
  a { color: blue; }
}
@layer components {
  .link { color: #1572B6; }   /* wins over base — even same specificity */
}
@layer utilities {
  .text-red { color: red; }   /* wins over components */
}

How Specificity Is Actually Calculated

When two rules target the same element, the browser scores each selector as three numbers — (IDs, classes, elements) — and the higher score wins.

SelectorIDsClassesElementsScore
p0010,0,1
.btn0100,1,0
nav .btn0110,1,1
#hero1001,0,0

Compare left-to-right: one ID beats any number of classes; one class beats any number of elements. If scores tie, the rule that appears later in the CSS wins (source order).

⚠️
!important is a code smell, not a fix

It overrides the whole cascade, so the next override needs another !important, and soon nothing is predictable. Fix specificity properly instead — usually by using a single, well-named class rather than deep #id div ul li a chains. Keep specificity flat and low.

🏋️ Practical Exercise

  1. Calculate the specificity of three different selectors.
  2. Override a class rule with an id rule.
  3. See an inline style beat a stylesheet rule.
  4. Use !important to override everything.
  5. Inspect the winning rule in DevTools.

🔥 Challenge Exercise

Given a paragraph targeted by an element selector, a class, an id, and an inline style, predict the final color using specificity rules, then verify in DevTools. Explain the specificity weights (inline, id, class, element) and why !important should be a last resort.

📋 Summary

  • Specificity score is (a, b, c): a = IDs, b = classes/attributes/pseudo-classes, c = elements/pseudo-elements.
  • Compare column by column left to right. Column A wins over any number of B or C.
  • Equal specificity → last rule in source wins (the cascade).
  • Inline styles beat all selector specificity (unless !important is involved).
  • !important is a nuclear option — overrides everything in the normal layer. Legitimate for utility classes (.hidden, .sr-only).
  • :where() is zero-specificity — write complex selectors without raising the score.
  • @layer creates cascade layers — later layers win regardless of specificity within the layer.
  • Keep selectors flat (1 class level) to avoid specificity wars.

Interview Questions

  • How is CSS specificity calculated?
  • What is the specificity order from highest to lowest?
  • How does !important affect specificity?
  • What wins when two selectors have equal specificity?
  • Why is high specificity hard to maintain?

FAQ

Does the order of selectors in the stylesheet affect specificity? +

No — only the selector type and count affect the specificity score. Source order only matters as a tiebreaker when two rules have the same specificity score. A class selector (.btn) always loses to an ID selector (#btn) regardless of where they appear in the stylesheet.

How many class selectors does it take to beat an ID? +

Technically infinite — specificity scores do not overflow between columns. Even .a.b.c.d.e.f.g.h.i.j.k (11 classes = score 0,11,0) does not beat #id (score 1,0,0), because column A is compared first and 1 beats 0. This is why using IDs in selectors creates a specificity problem that can only be resolved with another ID or !important.

What is @layer and when should I use it? +

@layer (CSS Cascade Layers, introduced in 2022) lets you group CSS rules into named layers with a defined order. Rules in a higher-priority layer win over all rules in lower-priority layers, regardless of specificity. This is especially useful when integrating third-party CSS (reset, component libraries) — put them in a low-priority layer so your application styles always win without needing !important. Browser support is above 93% as of 2026.