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
- IDs: 1 (#nav)
- Classes/pseudo-classes: 2 (.link, :hover)
- Elements: 0
- Score: 0,1,2,0
body header .logo img
- IDs: 0
- Classes/pseudo-classes: 1 (.logo)
- Elements: 3 (body, header, img)
- Score: 0,0,1,3
div nav ul li a span
- Six elements, zero classes, zero IDs
- Score: 0,0,0,6
- This loses to one class every time
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
- Audit your codebase — Search for
#in your CSS. Replace IDs with classes where you can. - Check nesting depth — If you're more than 3 levels deep, simplify. One class on the target element beats five ancestors.
- Remove !important — Except for resets. Every single one.
- Use BEM or similar — Flat specificity within components.
.card__titlebeats nested chains every time. - Verify with DevTools — Before blaming the cascade, check if something else is targeting your element with higher specificity.
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.