Decoding the CSS Cascade: How Browsers Resolve Conflicting Declarations
Why does that `.highlight` style sometimes lose to a `.button` rule you didn’t expect? Let’s untangle how the cascade actually works.
October 9, 2026

Ever added a CSS rule, sure it should apply, but the browser stubbornly sticks to the old style? You tweak selectors, add !important, but still something else wins that you didn’t expect. I’ve been there more times than I care to count.
CSS feels simple at first: write a selector, set a property, watch the change. But once your stylesheets grow, frameworks pile on layers, and browsers juggle conflicting declarations, things get messy. The secret sauce behind it all is the CSS cascade , the rules browsers use to decide which style actually applies when multiple declarations fight.
I recently dug into how the cascade really works and it changed how I debug CSS issues. Along the way, I noticed how SCSS-generated CSS and its nesting can subtly affect cascade outcomes. Here’s what I learned, with examples you can try yourself.
When CSS surprises you
Imagine this:
.button {
color: blue;
}
.highlight {
color: red;
}
And your HTML:
<button class="button highlight">Click me</button>
You expect the button text to be red because .highlight is last and looks more specific, right? But it stays blue. What’s going on?
Turns out, .button and .highlight have the same specificity (both one class), but the cascade also considers origin and source order. The .button rule might come from a stylesheet loaded later, or even from a user agent stylesheet or browser extension.
With SCSS, you might write nested selectors that compile to different source orders than you expect, which can change which rule wins.
Let’s break down what’s actually happening.
The cascade’s main players
The cascade resolves conflicts between multiple CSS declarations targeting the same element and property. It prioritizes declarations based on four factors:
- Origin , Where the CSS comes from (user agent, user, author, inline).
- Importance , Whether a declaration is marked
!important. - Specificity , How precisely a selector targets an element.
- Source order , Which declaration appears last in the CSS when everything else ties.
We’ll walk through each with examples , including how SCSS can affect source order.
1. Origin: Browser vs you
CSS rules come from different origins:
- User agent stylesheets , The browser’s default styles (like gray buttons).
- User stylesheets , Styles a user applies to override websites (rare for devs).
- Author stylesheets , Your CSS files.
- Inline styles , Styles written directly on elements via the
styleattribute.
The cascade ranks origins roughly like this:
User !important > Author !important > Author normal > User normal > User agent
Inline styles have very high specificity and usually win unless overridden by !important from author or user styles.
Example:
<button class="btn" style="color: green">Save</button>
.btn {
color: red !important;
}
Here, the .btn rule is an author !important style, which beats inline styles without !important. So the text will be red, not green.
2. Importance: The !important trump card
Adding !important bumps a declaration above normal ones, even with lower specificity.
.button {
color: blue !important;
}
.highlight {
color: red;
}
Now .button wins because of !important, even though .highlight comes later.
Be careful: overusing !important turns your CSS into a debugging nightmare. Use it sparingly.
3. Specificity: Counting your selector’s weight
Specificity measures how precisely a selector targets an element. It’s roughly:
- Inline styles: 1,0,0,0
- IDs: 0,1,0,0 per ID
- Classes, attributes, pseudo-classes: 0,0,1,0 each
- Element names and pseudo-elements: 0,0,0,1 each
Example:
#nav a.active {
color: blue;
}
.nav a {
color: red;
}
#nav a.active is more specific because it has an ID selector, so it wins.
How SCSS nesting can affect specificity and source order
SCSS lets you nest selectors, which compiles to multiple selectors in CSS. For example:
.button {
color: blue;
&.highlight {
color: red;
}
}
Compiles to:
.button {
color: blue;
}
.button.highlight {
color: red;
}
Here, .button.highlight is more specific than .button alone, so it wins.
But if you write:
.highlight {
color: red;
}
.button {
color: blue;
}
The compiled CSS source order has .highlight first, .button later , so .button wins if specificity ties.
SCSS source order depends on how you organize your files and nesting, which affects cascade.
4. Source order: Last rule wins
When origin, importance, and specificity tie, the declaration that appears last in the CSS wins.
For example:
.button {
color: blue;
}
.button {
color: red;
}
The button text will be red because the second rule comes later.
With SCSS, remember that nesting and imports can reorder rules unexpectedly.
Why specificity can be tricky
Sometimes inline styles override CSS rules with !important. That’s because inline styles have very high specificity, but !important can trump inline styles if the !important comes from the author or user origin.
Also, attribute selectors and pseudo-classes add to specificity, so adding [type="button"] or :hover can change which rule wins.
Debugging cascade issues: Tools and tricks
When a style won’t apply:
- Use your browser’s devtools Computed Styles pane to see which rules apply and which get overridden.
- Look for specificity and origin badges.
- Check for
!importantflags , they usually explain why a rule is winning. - Review source order in your CSS files and how SCSS compiles your styles.
- Inline styles and JavaScript-injected styles often cause surprises.
Real-world example: Unexpected background color
I once had a card component whose background wouldn’t change despite multiple CSS attempts:
.card {
background-color: white;
}
.highlighted {
background-color: yellow !important;
}
Applied like this:
<div class="card highlighted">...</div>
But the background stayed white. Devtools showed an inline style:
<div class="card highlighted" style="background-color: white;">...</div>
Inline styles have higher specificity than author styles. The !important on .highlighted didn’t override the inline style because the inline style wasn’t marked !important.
The fix? Remove the inline style or add !important via JavaScript if needed. Better yet, avoid inline styles for such properties.
What about inheritance?
Some CSS properties inherit by default (like color and font-family). Others don’t (like margin and border). The cascade applies after inheritance, so if you see unexpected styles, inheritance might be involved. But inheritance only fills in when no declaration exists , it doesn’t override declarations.
Wrapping up the cascade puzzle
The cascade isn’t just "last rule wins." It’s a layered system balancing where styles come from, how important they are, how specific selectors are, and where they appear in your CSS.
Understanding these layers helps you debug tricky styling issues faster and write more predictable CSS. When that style refuses to apply, don’t just add !important; pause, check origins, specificity, and source order , including how your SCSS compiles , and your fix will land smoother.
Next time your CSS fights back, remember the cascade’s secret hierarchy and use it wisely.