CSS Specificity Explained for Developers

What CSS Specificity Actually Is

CSS specificity determines which styles win when multiple rules target the same element. It's the reason your button stays blue when you swore you changed it to red. It's a scoring system built into the browser, and if you don't understand it, you'll spend hours fighting your own stylesheets.

No framework or methodology saves you from this. Not Tailwind, not BEM, not CSS-in-JS. You need to know how specificity actually works.

The Specificity Hierarchy

Specificity is calculated using three categories. Think of it as a three-digit score: inline styles, IDs, classes/attributes/pseudo-classes, and elements/pseudo-elements.

Selector Type Specificity Value Example
Inline styles 1,0,0,0 style="color: red"
ID selectors 0,1,0,0 #header
Class, attribute, pseudo-class 0,0,1,0 .btn, [type="text"], :hover
Element, pseudo-element 0,0,0,1 div, ::before

The browser compares these like numbers. 1,0,0,0 beats 0,9,9,9 every time. That's how much inline styles dominate.

How to Calculate Specificity

Count your selectors in each category. Don't add them together like decimal numbers—keep them as separate columns.

Examples

#nav .link:hover

body header .logo img

div nav ul li a span

This is why chaining element selectors is pointless. You're just making your code harder to read without gaining any real power.

!important Breaks Everything

!important ignores specificity entirely. It wins. Always.

The problem: once you use !important, you need another !important to override it. Then another. Then you're commenting out blocks to figure out why your color won't change.

Use it for browser reset overrides only. Nothing else. If you're using !important in your component styles, you have a specificity problem you're papering over.

Common Specificity Mistakes

Nesting selectors too deep

Scss nesting looks clean but produces selectors like .parent .child .wrapper .card .title span. That's specificity 0,0,4,2. Any modifier class will struggle against it.

Overusing ID selectors

IDs have insane specificity. Once you use #sidebar, you need another ID to override anything inside it. Use classes instead. Classes go up to 0,0,9,0 easily without causing problems.

Not knowing the universal selector

* has zero specificity. It changes nothing. But * combined with something else? Still zero. Only actual selectors count.

Confusing pseudo-classes with pseudo-elements

:hover and :focus are pseudo-classes (0,0,1,0). ::before and ::after are pseudo-elements (0,0,0,1). Different categories, different weights.

How to Debug Specificity Wars

Open DevTools. Inspect the element. Look at the Styles panel. The winning rule shows with no strikethrough. Any rule with strikethrough is losing specificity.

Still confused? Right-click the element, choose "Force state" and toggle :hover, :active, etc. Sometimes a pseudo-class you forgot about is winning.

Chrome DevTools now shows specificity values directly next to each selector. Use that. Stop guessing.

Getting Started: Fix Your Specificity Now

The Short Version

Specificity is a four-column scoring system. Inline wins over IDs wins over classes wins over elements. Keep your selectors shallow. Use classes, not IDs. Avoid !important. DevTools shows you exactly what's winning—use it.

You don't need to memorize every edge case. You need to understand that specificity exists, know how to check it in DevTools, and stop writing selectors that look like file paths.