๐ŸชบToolNest
developer7 min read

CSS Specificity Explained: Why Your Styles Aren't Applying

Master CSS specificity โ€” how the cascade works, the specificity scoring system, the !important rule, and practical strategies to avoid specificity wars.

TN

ToolNest Team

July 5, 2025

#CSS#specificity#styling#web development

Why Isn't My CSS Working?

You've added a CSS rule, but the element still looks wrong. You check the browser DevTools, and the style shows as crossed out โ€” overridden. The culprit is almost always specificity: another CSS rule has higher priority, so your rule is ignored.

Understanding specificity is fundamental to writing maintainable CSS.

The CSS Cascade

When multiple CSS rules target the same element and set the same property, the browser must choose one. This resolution process is the cascade, and it works in this priority order:

  1. Origin โ€” Browser default styles < Author styles < User styles < !important
  2. Specificity โ€” More specific selectors win
  3. Order โ€” When specificity is equal, the later rule wins

The Specificity Score (A, B, C)

Specificity is calculated as a three-part score: (A, B, C)

Component What counts Examples
A (hundreds) Inline styles, :is()/:not()/:has() argument specificity style="..."
B (tens) ID selectors #header, #nav
C (ones) Class selectors, attribute selectors, pseudo-classes .button, [type="text"], :hover
(0,0,0) Element/type selectors, pseudo-elements div, p, ::before

The universal selector * has zero specificity.

Comparing scores: Compare A first; if equal, compare B; if equal, compare C. The highest score wins.

Examples:

p                   /* (0,0,1)  โ€” 1 element */
.button             /* (0,1,0)  โ€” 1 class */
#header             /* (1,0,0)  โ€” 1 ID */
p.button            /* (0,1,1)  โ€” 1 element + 1 class */
#nav .button        /* (1,1,0)  โ€” 1 ID + 1 class */
#nav #sub .button   /* (2,1,0)  โ€” 2 IDs + 1 class */
div#nav p.button    /* (1,1,2)  โ€” 1 ID + 1 class + 2 elements */
style=""            /* (1,0,0,0) โ€” inline style, always wins over stylesheets */

Comparing:

  • .button (0,1,0) vs p.button (0,1,1): p.button wins
  • #nav (1,0,0) vs .button.active.large (0,3,0): #nav wins โ€” one ID beats any number of classes
  • p.button (0,1,1) vs .button:hover (0,2,0): .button:hover wins (0,2,0 > 0,1,1)

Common Specificity Scenarios

Scenario 1: Component styles overridden by page styles

/* Component CSS */
.button { color: white; }

/* Page CSS โ€” loaded later */
nav a { color: blue; }  /* (0,0,2) vs (0,1,0) โ€” .button wins */

Actually .button wins here. But what about:

#header nav a { color: blue; }  /* (1,0,2) โ€” wins over .button (0,1,0) */

Now the nav link style wins due to the ID.

Scenario 2: Utility class not applying

/* Framework CSS */
.card .card-title { font-size: 20px; }  /* (0,2,0) */

/* Your utility class */
.text-sm { font-size: 14px; }  /* (0,1,0) โ€” loses */

Your utility class loses because the framework's nested class selector has higher specificity.

!important

Adding !important to a declaration overrides all specificity rules:

.button { color: white !important; }

Even inline styles and ID selectors won't override an !important declaration (except another !important with equal or higher specificity).

When to use !important:

  • Legitimate use cases are rare: overriding third-party library styles you can't modify, utility classes in a design system
  • Never use it to "fix" a specificity problem in your own code โ€” it's technical debt that cascades

The !important specificity battle: Once you use !important, the only way to override it is another !important. This is how "specificity wars" start โ€” each override requires escalating specificity or !important, making CSS increasingly unmaintainable.

Inheritance vs Specificity

Not all CSS properties cascade โ€” some inherit from parent elements:

Properties that inherit: color, font-*, line-height, text-*, list-*, cursor, visibility

Properties that don't inherit: background, border, padding, margin, width, height, display

You can force inheritance: background: inherit;

Inherited values have lower priority than any explicit rule, even the universal selector.

How to Avoid Specificity Wars

1. Keep specificity low. Use classes, not IDs, for styling. A single class is almost always sufficient.

2. Use the BEM methodology โ€” Block__Element--Modifier naming creates unique class names, eliminating the need for nested selectors:

.card { }
.card__title { }
.card--featured { }

3. Avoid nesting selectors unnecessarily. .nav .nav-item .nav-link creates high specificity. Just .nav-link is better if the class is specific enough.

4. Use CSS custom properties. Passing values through CSS variables doesn't involve specificity at all.

5. Use :is() and :where() strategically. :where() always has zero specificity โ€” useful for base styles that you want to be easily overridden:

:where(h1, h2, h3) { margin-top: 1em; }  /* specificity (0,0,0) */

6. Layers (CSS @layer) โ€” A modern CSS feature that lets you explicitly define cascade layers, giving you control over which rules take precedence regardless of specificity.

Use our free CSS Specificity Calculator to instantly calculate the specificity score of any CSS selector.

Share this article