// blog/developer/
Back to Blog
Developer · Published August 17, 2026 · 10 min read · By Toine

CSS Specificity Explained: How Browsers Pick Styles

CSS Specificity Explained: How Browsers Pick Styles

You write a CSS rule. It does not apply. You add another, more specific. Still nothing. You add !important out of frustration, and the stylesheet becomes a tangled mess nobody wants to touch. The cause is almost always specificity.

CSS specificity is the algorithm browsers use to decide which style declarations get applied when multiple rules target the same element. It is not random. It follows a precise scoring system you can calculate and predict.

Once you understand how it works, debugging CSS gets much easier. You stop fighting the cascade and start working with it. Fewer overrides, fewer !important flags, stylesheets that are easier to maintain.

* * *

The Specificity Scoring System

Specificity is calculated as a four-part score, often written as (a, b, c, d). Each part corresponds to a category of selector.

Inline styles (a): styles applied directly in the HTML style attribute. Score: (1, 0, 0, 0). These beat almost everything except !important.

IDs (b): selectors that use #id. Each ID in the selector adds 1 to this column. Score per ID: (0, 1, 0, 0).

Classes, attributes, pseudo-classes (c): selectors using .class, [type="text"], :hover, :nth-child(), etc. Each one adds 1 to this column. Score per item: (0, 0, 1, 0).

Elements and pseudo-elements (d): selectors using tag names like div, p, h1, or pseudo-elements like ::before. Score per item: (0, 0, 0, 1).

Examples:

  • p scores (0, 0, 0, 1)
  • .card scores (0, 0, 1, 0)
  • #header .nav a scores (0, 1, 1, 1)
  • div.sidebar ul li.active a:hover scores (0, 0, 3, 4)

The comparison works left to right. A selector with (0, 1, 0, 0) always beats (0, 0, 15, 15), no matter how many classes and elements are stacked up. One ID outweighs any number of classes.

* * *

Common Specificity Mistakes and How to Fix Them

The most frequent mistake is over-qualifying selectors. Writing div.container ul.nav li.item a.link when .nav-link would do the same job. The long selector has high specificity, which means overriding it later requires an equally specific (or more specific) selector.

Another common issue is using IDs for styling. IDs are unique per page, which is great for JavaScript hooks and anchor links, but terrible for CSS. A single #sidebar in your stylesheet creates a specificity wall that forces everything targeting sidebar elements to also use the ID.

The !important flag overrides all specificity calculations. It wins regardless of the selector score. But when two rules both have !important, specificity decides between them. This leads to !important wars where developers keep adding the flag to override previous !important declarations.

Better alternatives:

  • Use classes for styling, reserve IDs for JavaScript and anchors
  • Keep selectors as short and flat as possible
  • Follow a naming convention like BEM (Block Element Modifier) to avoid nesting
  • Use the cascade intentionally by ordering your stylesheets from generic to specific
  • If you must override a third-party library, use a single class with higher specificity rather than !important

After restructuring your selectors, run your stylesheet through the CSS Minifier to strip whitespace and reduce the file size for production.

Browser DevTools showing conflicting CSS rules with specificity indicators
Browser DevTools showing conflicting CSS rules with specificity indicators
* * *

The Cascade: When Specificity Is Equal

When two selectors have identical specificity scores, the cascade uses source order as the tiebreaker. The rule that appears later in the stylesheet wins. This is why the order of your CSS files and the order of rules within those files matters.

The full cascade algorithm evaluates rules in this priority:

  1. Origin and importance (user agent styles, author styles, !important author styles)
  2. Specificity score
  3. Source order (later wins)

This means you can structure your stylesheets strategically. Put your base reset and typography styles first. Then component styles. Then utility classes. Then overrides. Each layer naturally has the opportunity to override the previous one because of source order.

CSS layers (@layer) introduced in modern browsers formalize this pattern. You can define named layers and control their priority explicitly:

`css @layer base, components, utilities; `

Rules in the utilities layer will override rules in components, which override base, regardless of specificity within those layers. It makes large stylesheets and third-party CSS integration much easier to manage without specificity wars.

* * *

Practical Strategies for Managing Specificity

The most maintainable approach is to keep specificity consistently low across your entire project. When every selector is a single class, overriding any rule is straightforward.

The one-class rule: aim for selectors that are a single class name. .card-title instead of .card .title. .nav-link--active instead of .nav li a.active. This keeps every selector at (0, 0, 1, 0) and makes source order the only factor.

Utility-first CSS (Tailwind, etc.): applies styles directly in HTML using utility classes. Specificity is uniformly low because each utility is a single class. Overrides happen by changing the HTML, not by writing competing CSS.

BEM naming: Block__Element--Modifier creates unique class names that avoid nesting. .card__header--highlighted is one class with specificity (0, 0, 1, 0), no matter how deep the component structure goes.

The :where() specificity eraser: the :where() pseudo-class always has zero specificity, regardless of what is inside it. This is incredibly useful for default styles that should be easy to override:

`css :where(.button) { background: gray; } .primary { background: blue; } `

The .primary class easily overrides :where(.button) because :where() contributes zero to specificity.

Visualise the cascade with quick experiments in the CSS Gradient Generator when you need to test how layered styles stack. Clean, simple stylesheets make specificity conflicts easier to spot during review.

Key takeaway

The most maintainable approach is to keep specificity consistently low across your entire project.

* * *

Debugging Specificity in Browser DevTools

Every modern browser shows you exactly which rules are applied, which are overridden, and why. In Chrome or Firefox DevTools, inspect an element and look at the Styles panel.

Overridden rules appear with a strikethrough. Hover over a rule to see its specificity score (Chrome shows this in newer versions). If a rule you expected to apply is struck through, compare its specificity with the winning rule.

Steps for debugging:

  1. Right-click the element and select Inspect
  2. In the Styles panel, find the property that is not working
  3. Look for the same property in other rules above it (higher specificity) or below it with !important
  4. Compare the selectors and calculate their specificity
  5. Adjust your selector to match or exceed the winning specificity, or restructure to avoid the conflict

The Computed tab shows the final computed value for every property and which rule set it. Click the arrow next to any value to jump to the winning rule.

For production, minify your final stylesheet with the HTML Minifier for the HTML and the CSS Minifier for styles. Smaller files load faster and reduce the amount of CSS the browser has to parse.

Diagram showing CSS specificity scoring from inline styles to element selectors
Diagram showing CSS specificity scoring from inline styles to element selectors
* * *

FAQ

Does the universal selector (*) have any specificity?

No. The universal selector * has zero specificity (0, 0, 0, 0). It matches everything but never beats any other selector. Combinators like >, +, and ~ also have zero specificity. Only the actual selectors (IDs, classes, elements) contribute to the score.

Is specificity different from importance?

Yes. Specificity determines which rule wins among rules of the same importance level. The !important flag elevates a rule to a higher importance level entirely. Within !important rules, specificity still decides the winner. Think of importance as a tier system: normal rules compete within their tier, important rules compete within theirs.

Can I see specificity scores in my IDE?

Some IDEs and extensions show specificity on hover. VS Code with the CSS extension displays specificity when you hover over a selector. Stylelint can also be configured to warn when specificity exceeds a threshold, helping enforce low-specificity conventions across a team.

How does specificity work with CSS-in-JS?

CSS-in-JS libraries like styled-components and Emotion generate unique class names at runtime. Because each class is unique, specificity conflicts between components are rare. The trade-off is that overriding styles from outside the component requires knowing the generated class name or using a wrapper with higher specificity.

Key takeaway

### Does the universal selector (*) have any specificity.