Specificity Rules

Difficulty: Intermediate

Specificity is the algorithm browsers use to determine which CSS declaration applies when multiple rules target the same element with conflicting property values. It is one of the most frequently asked CSS interview topics because understanding it is essential for debugging styling issues and writing maintainable CSS. Many frustrating 'why won't my style apply?' moments are caused by specificity conflicts.

Specificity is calculated as a four-part score, often represented as (a, b, c, d): 'a' is 1 if the style is inline (style attribute), 'b' counts the number of ID selectors, 'c' counts the number of class selectors, attribute selectors, and pseudo-classes, and 'd' counts the number of element (type) selectors and pseudo-elements. The universal selector (*), combinators (+, >, ~, space), and the :not() pseudo-class itself contribute zero specificity (though the selectors inside :not() do count).

Specificity comparisons work left-to-right, with each position being more significant than all positions to its right. One ID selector (0,1,0,0) beats any number of class selectors - even 100 classes (0,0,100,0) cannot override a single ID. Similarly, one class (0,0,1,0) beats any number of element selectors. This is why treating specificity as a decimal number (like 0100 vs 0010) is technically misleading - the positions are independent, not digits in a base-10 number.

When two selectors have exactly the same specificity, the cascade falls back to source order - the rule that appears later in the CSS wins. This is why the order of your CSS file matters and why CSS reset/normalize styles are placed at the top. For linked stylesheets, the order of <link> tags in the HTML determines the order. Styles from a later stylesheet override styles from an earlier one at equal specificity.

To override high-specificity styles without resorting to !important, you have several strategies. You can increase your selector's specificity by adding more classes or repeating a class (.card.card has specificity 0,0,2,0). You can restructure your CSS to avoid the conflicting rule entirely. You can use CSS Layers (@layer) which is a modern CSS feature that lets you control the priority of entire groups of styles regardless of specificity. As a last resort, !important overrides all normal declarations, but creates maintenance problems and should be reserved for utility classes and third-party overrides.

A practical rule of thumb for writing maintainable CSS: keep specificity as low as possible. Use single class selectors (.card-title) instead of nested selectors (.card .title). Avoid ID selectors for styling. Never use inline styles except for truly dynamic values set by JavaScript. And structure your CSS so that more specific overrides come after general styles - progressive enhancement of specificity.

Code examples

Specificity Calculation Examples

/* Specificity: (inline, IDs, classes, elements) */

*                          { }  /* 0,0,0,0 */
p                          { }  /* 0,0,0,1 */
p.intro                    { }  /* 0,0,1,1 */
p.intro.featured           { }  /* 0,0,2,1 */
#sidebar                   { }  /* 0,1,0,0 */
#sidebar .widget           { }  /* 0,1,1,0 */
#sidebar .widget h3        { }  /* 0,1,1,1 */
#sidebar #search           { }  /* 0,2,0,0 */

/* Pseudo-classes count as classes */
a:hover                    { }  /* 0,0,1,1 */
li:nth-child(2)            { }  /* 0,0,1,1 */
input:not(.large)          { }  /* 0,0,1,1 (input + .large) */

/* Pseudo-elements count as elements */
p::first-line              { }  /* 0,0,0,2 */
.card::before              { }  /* 0,0,1,1 */

/* Inline styles */
/* <div style="color: red;"> */ /* 1,0,0,0 */

Each selector component adds to a specific position. IDs add to position b, classes/pseudo-classes/attributes add to position c, elements/pseudo-elements add to position d. Higher positions always outweigh lower ones.

Specificity Battles

/* Which color wins for <p id="intro" class="lead text"> ? */

p { color: black; }                     /* 0,0,0,1 */
.lead { color: gray; }                  /* 0,0,1,0 - beats p */
p.lead { color: blue; }                 /* 0,0,1,1 - beats .lead */
.lead.text { color: green; }            /* 0,0,2,0 - beats p.lead */
#intro { color: red; }                  /* 0,1,0,0 - WINS (ID beats all classes) */

/* Even 20 classes can't beat 1 ID: */
.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t { color: purple; }
/* 0,0,20,0 - still loses to #intro (0,1,0,0) */

/* Source order tiebreaker (same specificity): */
.card { background: white; }             /* 0,0,1,0 */
.card { background: #f9fafb; }           /* 0,0,1,0 - wins (later in source) */

/* !important overrides everything: */
.subtle { color: gray !important; }      /* Beats even inline styles */

Specificity is NOT a single number - it is a tuple compared left-to-right. One ID always beats any number of classes. When specificity is tied, the last rule in source order wins. !important trumps all normal declarations.

Strategies to Override Without !important

/* Problem: library uses a high-specificity selector */
#sidebar .widget .title {
  color: black;  /* 0,1,2,0 */
}

/* Strategy 1: Match or exceed specificity */
#sidebar .widget .title.custom-title {
  color: blue;   /* 0,1,3,0 - wins */
}

/* Strategy 2: Repeat a class for specificity boost */
.title.title {
  /* Repeating a class is valid and doubles its weight */
  /* 0,0,2,0 - but still loses to ID-based selectors */
}

/* Strategy 3: Use CSS Layers (modern CSS) */
@layer library {
  #sidebar .widget .title {
    color: black;
  }
}

@layer custom {
  .title {
    color: blue;  /* Wins because 'custom' layer has higher priority */
  }
}

/* Strategy 4: Restructure - avoid deep nesting */
/* Instead of #sidebar .widget .title, use a flat class: */
.widget-title {
  color: blue;   /* 0,0,1,0 - low specificity, easy to override */
}

/* Last resort: !important (use sparingly) */
.override {
  color: red !important;
}

Prefer restructuring CSS and using classes over escalating specificity. CSS @layer is the modern solution for controlling priority across stylesheets. Reserve !important for utility classes and overriding third-party styles.

Specificity Debugging

/* Common debugging scenario: */

/* Your styles: */
.card .title {
  font-size: 18px;  /* 0,0,2,0 */
}

/* A framework's styles (loaded after yours): */
.card .title {
  font-size: 16px;  /* 0,0,2,0 - same specificity, but LATER = wins */
}

/* Fix 1: Increase specificity */
.card .title.my-title {
  font-size: 18px;  /* 0,0,3,0 - higher specificity, wins */
}

/* Fix 2: Load your stylesheet after the framework */
/* <link href="framework.css">
   <link href="my-styles.css">  -- yours comes last -- */

/* Debugging tip: use browser DevTools */
/* Right-click element > Inspect > Styles panel shows:
   - All matching rules in specificity order
   - Crossed-out rules that were overridden
   - The computed specificity of each selector */

/* Quick specificity reference: */
/* Inline style      > ID > Class > Element */
/* 1,0,0,0           0,1,0,0   0,0,1,0   0,0,0,1 */

Use browser DevTools to debug specificity issues. The Styles panel shows which rules are being overridden and why. The Computed tab shows the final applied value for each property.

Key points

Concepts covered

Specificity Calculation, Specificity Tiebreaking, Overriding Styles, Specificity Score, !important, Selector Weight