aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Frontend & UI

CSS Specificity: Which Rule Wins?

You set a color on an element from three different places, yet the browser picks the one you didn't want. This is where CSS specificity comes in: it is the priority system that decides which rule wins when several declarations target the same element. Once you understand specificity, the hours lost to "why isn't this style applying?" disappear. Instead of sprinkling random !important everywhere, you see exactly why a rule does or doesn't take effect.

What the cascade is and where specificity fits

The "Cascading" in CSS is the process the browser uses to order conflicting rules. To pick a winner, it walks through these steps in order:

  • Origin and importance: where the style comes from (browser defaults, user styles, author styles) and whether it carries !important.
  • Specificity: how "specific" the selector is — our main focus here.
  • Source order: if everything else is equal, the rule written last in the document wins.

So specificity does not decide on its own; it is one rung of the cascade. But in day-to-day work it is the rung that confuses people the most and causes the most conflicts.

How specificity is calculated: a three-part value

Think of every selector as getting a value with three components, usually written as a triple (a, b, c):

  • a — ID selectors: each ID like #header scores one point.
  • b — Classes, attributes and pseudo-classes: each .btn, [type="text"], :hover scores one point.
  • c — Elements and pseudo-elements: each div, a, ::before scores one point.

Comparison runs left to right: first compare a, then b if tied, then c. A few examples:

/* (0,0,1) — single element */
p { color: black; }

/* (0,1,1) — one class + one element */
p.intro { color: blue; }

/* (1,0,0) — single ID */
#main { color: green; }

/* (1,1,1) — ID + class + element */
#main p.intro { color: red; }

If they all target the same <p class="intro">, the winner is #main p.intro with the highest value, so the color is red. One crucial detail: the values are not added in base ten. Eleven classes (0,11,0) will never beat a single ID (1,0,0). A single 1 in the a column outranks any number of points in the b column.

Selectors that score nothing, and special cases

Some things contribute zero to specificity:

  • The universal selector * and combinators (>, +, ~, whitespace) add nothing.
  • :where() always produces zero specificity, no matter what you put inside it. This is perfect for writing "soft" defaults that are easy to override.
  • :is() and :not() add nothing themselves but take the score of the most specific selector inside them. For example, :is(#a, .b) counts as much as an ID.
/* a rule written with :where() has zero specificity */
:where(.card) a { color: teal; }

/* even a single element selector beats it (0,0,2 > 0,0,1) */
.card a { color: orange; }

!important and inline styles: the order breakers

Two things sit outside the normal specificity calculation. The first is inline styles, the style attribute, which is stronger than any selector-based rule (conceptually a fourth, leftmost column). The second is !important: adding it to a declaration moves that declaration into a separate, higher layer.

<p id="x" style="color: green">Hello</p>

#x { color: red; }              /* inline wins → green */
#x { color: blue !important; }  /* !important even beats inline → blue */

When two !important declarations clash, normal specificity and source order decide between them again. The practical advice is clear: treat !important as a last resort. Once you start using it, overriding it requires another !important, and your stylesheet turns into an escalation war.

Avoiding specificity wars in practice

The goal is to work with low and consistent specificity:

  • Use classes instead of IDs: IDs bring needlessly high scores in styling. Keep your selectors class-based.
  • Keep selectors shallow: instead of .nav ul li a, target a single class like .nav-link directly.
  • Use cascade layers: with @layer you set layer priority explicitly; a later layer beats an earlier one regardless of specificity.
  • Use :where() for soft defaults so consumers of a component can override styles easily.

Open the "Styles" panel in your browser's DevTools: struck-through declarations show instantly which rule was overridden and which won. Reading that panel instead of guessing is the fastest way to internalize specificity.

Frequently Asked Questions

What happens on equal specificity?

If origin, importance and specificity are all equal, source order decides: the rule defined last in the CSS wins. That is why placing an override after the existing rule is often enough.

Does !important always win?

Among author styles it almost always beats normal declarations. But if another !important declaration has higher specificity or comes later, that one wins. Where possible, build a structure that never needs !important at all.

What is the difference between :is() and :where()?

Both group a selector list and match the same elements. The difference is specificity: :is() takes the score of the most specific selector inside it, while :where() always produces zero.

If your style conflicts have grown and your CSS has turned into an unsustainable pile of !important, I can help you move the architecture to a class-based, layered structure. To discuss a project, get in touch with me.

Bu kategorideki tüm yazılar →

Devamı için