CSS Specificity and the Cascade: Why Your Style Isn't Applying

The single fastest way to spot a stressed-out codebase is to grep for !important. On one Laravel project I inherited, the CSS had 187 of them — every one of them a developer (probably me on a bad day) who lost a specificity fight and rage-quit. Once you actually understand how the cascade picks a winner — importance first, then specificity, then source order — you stop needing that escape hatch. This is the mental model I run through every time two rules collide, explained with the exact examples that finally made it click for me.
The specificity score
Every selector gets a 3-part score (A, B, C):
- A — number of ID selectors
- B — number of class, attribute, and pseudo-class selectors
- C — number of element and pseudo-element selectors
#main .card h2 /* 1, 1, 1 */
.card .title /* 0, 2, 0 */
h2 /* 0, 0, 1 */
#main /* 1, 0, 0 */Higher A wins first, then higher B, then higher C. If everything ties, the rule that comes later in the stylesheet wins.
Inline styles and !important
- Inline style attributes beat any selector in a stylesheet
- !important beats everything except another !important higher in the chain
- Use !important only as a last resort — usually in user agents or override utilities
The cascade in plain English
- Origin and importance — author > user > browser, !important flips the order
- Specificity — higher score wins
- Source order — last rule wins
Modern tools that change the game
@layer
@layer reset, base, components, utilities;
@layer components { .btn { background: blue; } }
@layer utilities { .bg-red { background: red; } }Layers let you control the cascade explicitly — utilities can always beat components without bumping specificity.
:where() to keep specificity at zero
:where(article h2) { color: navy; } /* specificity 0,0,0 */Common mistakes
- Using IDs in CSS (too specific to override)
- Long selector chains like .a .b .c .d
- Reaching for !important before reading the rule it conflicts with
Frequently asked questions
Does !important actually break my CSS?
It doesn't break it, but it creates override chains that are painful to maintain. Use it sparingly.
Why do my Tailwind utilities sometimes lose?
Often because a component CSS rule has higher specificity. Tailwind v4's @layer integration solves most of this.
External references
Enjoyed this article?
Share it with a fellow developer or explore more tutorials in our blog.
More articles