Skip to main content
TF
By Rohit V.8 min readArticle

CSS Specificity Explained (Without the Headache)

TF
ToolsFuel Team
Web development tools & tips
A developer comparing layouts and code on a desk

Photo by Unsplash on Unsplash

What is CSS specificity?

Almost everyone's first real CSS frustration is the same: you write a rule, it doesn't apply, and nothing looks broken. The culprit is nearly always specificity, quietly deciding another rule wins.

>
Quick answer: CSS specificity is how the browser decides which rule wins when two rules target the same element and set the same property. Each selector gets a score based on the kinds of parts it uses — IDs count more than classes, which count more than element names. The higher-specificity rule wins, and only when specificity ties does the last rule in the source order break the tie. Understanding the scoring is what turns 'why won't this apply?' into a five-second fix.

So the cascade isn't just 'last rule wins,' which is the mental model that leaves people baffled. Last-one-wins is only the tiebreaker. Before that, the browser compares how specific each selector is, and a more specific selector earlier in your file beats a less specific one later. Once you can eyeball which selector is more specific, the mystery evaporates.

How is specificity actually calculated?

Specificity is usually written as three numbers, like (0,1,2), representing three buckets you count up from a selector. Think of it as three columns that don't carry over into each other — a high count in a lower column never beats a single point in a higher one.

The first and highest column is IDs (`#header`). The second is classes, attribute selectors, and pseudo-classes (`.nav`, `[type="text"]`, `:hover`). The third and lowest is element types and pseudo-elements (`div`, `::before`). You count how many of each a selector contains. So `#header .nav a` is (1,1,1): one ID, one class, one element. `.nav a` is (0,1,1). The ID rule wins because the first column is higher, full stop, no matter what's in the other columns.


The non-carrying part is what surprises people. A selector with eleven classes is (0,11,0), and it still loses to a single ID at (1,0,0), because you compare column by column from the left. You can't out-class an ID. Inline styles sit even above all of this, effectively a fourth column to the left, which is part of why inline styles are so hard to override from a stylesheet.

Why won't my CSS rule apply?

When a rule you wrote refuses to take effect, specificity is the first suspect, and the fix is usually to compare the two competing selectors rather than to add more code. Open your browser devtools, inspect the element, and look at the styles panel — the browser shows you which rule won and crosses out the ones that lost. That crossed-out line is your answer.

Nine times out of ten, the winning rule is more specific than yours. Maybe it targets `#sidebar .widget` while you wrote `.widget`, so its ID gives it a higher score no matter where each sits in the file. The instinctive fix is to make your selector more specific to match or beat it — add the parent, add a class — but that starts an arms race that makes the stylesheet worse over time.


The better habit is to keep specificity low and even across your codebase, so rules compete on source order rather than escalating scores. When most of your selectors are single classes, the cascade becomes predictable again and 'last one wins' actually holds. I lean on browser devtools for this constantly; the
Chrome DevTools features worth knowing piece covers the panels that make specificity conflicts obvious at a glance.

When should you use !important (and when not to)?

`!important` is the nuclear option. Adding it to a declaration lets it override normal specificity entirely — an `!important` rule beats any non-important rule regardless of scores. It feels like a fix, and that's exactly why it's a trap.

The problem is that it doesn't end the fight, it just escalates it. Once you use `!important` to win one battle, the only way to override that later is another `!important`, and soon your stylesheet is a stack of overrides fighting each other with no clear winner. The specificity system you were trying to shortcut is now completely scrambled, and debugging gets harder, not easier. I've inherited codebases like that, and untangling them is genuinely painful.


There are a few legitimate uses — overriding third-party styles you can't edit, or utility classes designed to always win — but for your own code, reaching for `!important` is almost always a sign the real problem is specificity that's crept too high somewhere else. Fix the root cause by lowering the competing selector instead of stacking another override. Save the nuclear option for when you genuinely don't control the other stylesheet.

How do you keep specificity manageable?

The long-term goal isn't to win specificity fights — it's to avoid having them. A few habits keep the cascade calm, and modern CSS even gives you tools to opt out of specificity where you want to.

The main habit is to style with classes and keep selectors flat. Prefer a single class like `.card-title` over a deep chain like `#main .card .header h2`. Flat, class-based selectors all score the same, so they compete on source order, which is predictable and easy to reason about. Avoid styling by ID where you can, since IDs spike specificity and are hard to override later. This is the discipline behind naming systems like BEM.


Modern CSS adds an escape hatch worth knowing: the `:where()` pseudo-class always has zero specificity, so anything wrapped in it is trivially overridable — great for base styles you want to set but never have fight your components. Its sibling `:is()` behaves the same for matching but takes the specificity of its most specific argument, so they're not interchangeable. Between flat class selectors and `:where()` for defaults, you can keep a whole stylesheet low and even, which is the state where CSS stops surprising you. If you're also wrangling layout on top of this, the
flexbox vs grid guide pairs well with a tidy specificity setup.

How do modern CSS features change the specificity game?

CSS has quietly gained features built specifically to tame the specificity problem, and they've changed how I structure stylesheets. The old game was avoiding fights; the new tools let you decide who wins on purpose.

The biggest is cascade layers, written with `@layer`. They let you define an explicit order of precedence between groups of styles, so a rule in a later layer beats one in an earlier layer regardless of specificity. That means you can drop a low-specificity base layer, a components layer, and a utilities layer, and know utilities always win without a single `!important`. It moves the decision from accidental specificity scores to a deliberate order you set.


Alongside that, the `:where()` pseudo-class I mentioned earlier gives you zero-specificity selectors on demand, which is perfect for resets and defaults you never want fighting your real styles. Pair `:where()` for defaults with `@layer` for structure and most specificity conflicts stop happening before they start.


MDN's reference on
specificity is the page I keep bookmarked when I need to double-check how a selector scores. But the more I lean on flat classes, layers, and `:where()`, the less I have to consult it — the whole point of these features is to make specificity something you configure once rather than debug repeatedly. That's the shift worth making: stop fighting the cascade and start arranging it.

Frequently Asked Questions

How is CSS specificity calculated?

It's scored in three columns: IDs, then classes/attributes/pseudo-classes, then elements/pseudo-elements. You count each type in a selector and compare column by column from the left. A higher column always wins, so one ID beats any number of classes.

Why is my CSS not being applied?

Usually a more specific rule is overriding yours. Inspect the element in browser devtools — the styles panel shows the winning rule and crosses out the losers, which tells you exactly what to beat. The [Chrome DevTools guide](/blog/chrome-149-devtools-features-worth-knowing-2026) covers the panels for this.

Does an ID always beat a class in CSS?

Yes. Specificity compares column by column from the left, and IDs sit in a higher column than classes, so a single ID beats any number of classes. That's why relying on IDs for styling makes rules hard to override.

Is it bad to use !important in CSS?

Usually, yes, for your own code. It overrides normal specificity but can only be overridden by another `!important`, which escalates into a tangled mess. It's mainly justified for overriding third-party styles you can't edit.

How do I stop fighting CSS specificity?

Keep selectors flat and class-based so they score the same and compete on source order. Avoid styling by ID, and use the zero-specificity `:where()` pseudo-class for base styles you want to be easily overridable.

Try ToolsFuel

23+ free online tools for developers, designers, and everyone. No signup required.

Browse All Tools