Home / Articles / Modern CSS /
CSS specificity explained
Stop guessing why your styles lose. Learn how specificity is calculated and how modern CSS gives you real control over the cascade.
The specificity algorithm
Every selector gets a three-part score: (ID, Class, Element). Higher left columns always win, regardless of how many lower-level selectors you stack.
/* (0, 0, 1) — one element */
p { color: oklch(50% 0 0); }
/* (0, 1, 0) — one class */
.text { color: oklch(55% 0.1 250); }
/* (1, 0, 0) — one ID */
#hero { color: oklch(60% 0.2 280); }
/* (1, 1, 1) — combined */
#hero .text p { color: oklch(40% 0 0); }
:is() inherits the highest specificity
:is() takes the specificity of its most specific argument. This is powerful but can surprise you when mixing IDs and classes.
/* specificity = (1, 0, 0) because #main is in the list */
:is(#main, .sidebar, footer) p {
color: oklch(45% 0.15 200);
}
:where() instead.:where() — zero specificity
:where() works identically to :is() but contributes zero specificity. This makes it ideal for default styles that consumers can easily override.
/* (0, 0, 0) — trivially overridable */
:where(.card, .panel) {
padding: 1.5rem;
border: 1px solid oklch(80% 0 0);
}
/* a single class override wins */
.card { padding: 2rem; }
@layer and the cascade
Cascade layers let you group rules so that layer order — not specificity — decides the winner between layers. Unlayered styles always beat layered ones.
@layer reset, base, components, utilities;
@layer reset {
* { margin: 0; box-sizing: border-box; }
}
@layer utilities {
.text-center { text-align: center; }
}
The !important escape hatch
!important reverses layer order: an !important rule in an earlier layer beats one in a later layer. This means reset-layer !important rules are nearly impossible to override — by design.
- Avoid
!importantin component code. - Reserve it for utility classes and accessibility overrides.
- Prefer
@layerordering to win specificity battles cleanly.
Practical tips
Keep specificity flat across your project. A class-only selector strategy avoids most conflicts.
- Use
:where()for base / default styles. - Use
:is()sparingly, and never with IDs inside. - Wrap third-party CSS in its own
@layer. - Check specificity in DevTools — Chrome and Firefox both display it.