Skip to main content

What is cascade in CSS

Cascade in CSS is the algorithm browsers use to resolve conflicts by selecting the winning style value for each property when multiple CSS rules target the same element.

Theory

TL;DR

  • Cascade picks one value per property from all matching rules, not one winning rule overall.
  • Sort order: Origin > Importance > Specificity > Source order.
  • !important flips priority within each origin tier, it does not skip specificity entirely.
  • A later rule wins only when specificity is equal.
  • A rule can lose color and still contribute font-size to the same element.

Quick example

html
<style> p { color: blue; } /* loses: lower specificity */ .highlight { color: red; } /* wins: class beats tag selector */ </style> <p class="highlight">This text is red.</p> <!-- red --> <p>This text is blue.</p> <!-- blue -->

Both rules match the first <p>. Cascade picks .highlight for color because a class selector (specificity 0,0,1,0) beats a tag selector (0,0,0,1). No conflict on any other property, so nothing else changes.

How cascade resolves conflicts

Browsers sort all matching declarations in this exact order:

  1. Origin and importance. User-agent styles (browser defaults) sit at the bottom. Your author styles come next. User styles from browser extensions or OS settings sit above those. Adding !important flips each tier: author !important beats normal author, user !important beats almost everything.

  2. Specificity. Scored as a four-part tuple: inline styles (1,0,0,0), IDs (0,1,0,0), classes and attributes and pseudo-classes (0,0,1,0), tags and pseudo-elements (0,0,0,1). Compare left to right. #header a (0,1,0,1) beats .menu a (0,0,1,1) because the ID column wins immediately.

  3. Source order. If everything else ties, the declaration that appears last in the document wins.

Key difference from what most people assume

Cascade does not apply one winning rule and discard the rest. It picks the winning value per property. A rule with color: red; font-size: 16px can lose color to another rule and still contribute font-size to the computed style. That merge step is where most people get confused, because they expect one rule to take over completely.

When to use each approach

  • Simple site, no conflicts - keep specificity low and rely on source order.
  • Component library conflicts - boost specificity with BEM-style selectors like .card__button--primary before reaching for !important.
  • Fighting third-party CSS - use !important as a last resort, not a first move. Inline styles are a better short-term option.
  • Theming system - layer your stylesheets: base, then theme, then user preferences. CSS @layer (supported in all major browsers since 2022) makes this explicit.
  • Debugging mystery styles - open DevTools "Styles" pane. It shows every matching declaration, which one won, and why the rest are crossed out.

How the browser handles this internally

Chrome's Blink engine parses all stylesheets into a cascade tree sorted by origin. During layout, it matches selectors for each element, scores specificity, and walks the tree. Highest origin/importance/specificity/order wins per property. Values that do not conflict merge into the computed style object. That last part, the merge, is the bit the cascade spec describes but most tutorials skip entirely.

Common mistakes

  • Assuming later rules always win

    css
    p { color: red; } /* first in code, but wins anyway */ div p { color: blue; } /* later in code, but loses */

    div p has specificity 0,0,0,2 and p has 0,0,0,1. Specificity beats source order.

  • Using !important as a quick fix

    I've watched teams go through !important wars that lasted weeks because one developer used it in a shared component and nobody could override it cleanly.

    css
    .btn { color: black !important; } /* theme overrides now broken */

    Fix: raise specificity instead. .theme-dark .btn { color: white; } works without poisoning everything downstream.

  • Thinking inline styles always win

    html
    <p style="color: blue" class="override">Text</p> <style>.override { color: red !important; }</style> <!-- Output: red -->

    !important beats inline styles. Only adding !important to the inline value itself would flip it back.

  • Ignoring user stylesheets

    Browser extensions inject CSS at the user-origin tier, which overrides your author styles without any warning. If something looks broken in a user's browser but not yours, check for extension interference. DevTools lets you emulate user stylesheets to test this.

Real-world usage

  • Tailwind CSS - utility classes like bg-red-500 win over base CSS via specificity or source order. Non-conflicting properties from your base still apply.
  • Bootstrap - .btn-primary (class plus tag specificity) overrides a generic .btn rule.
  • Styled Components / Emotion - generates scoped class names, but cascade still applies across libraries loaded on the same page.
  • Material UI - CSS custom properties combined with cascade handle theme overrides at the property level.

Follow-up questions

Q: What is the full cascade sort order?
A: Origin and importance first (user-agent < user < author, !important flips within each tier), then specificity (inline > ID > class/attr/pseudo-class > tag), then source order.

Q: How does specificity calculate?
A: It is a four-part tuple (A, B, C, D): A equals 1 for inline styles, B counts IDs, C counts classes and attributes and pseudo-classes, D counts tags and pseudo-elements. Compare left to right. Combinators like > and + add nothing to the score.

Q: What happens when specificity is equal?
A: The last declaration in document order wins for normal rules. For !important declarations it reverses: the first one wins.

Q: Does cascade work the same inside Shadow DOM?
A: No. Shadow DOM encapsulates its own cascade. Constructable stylesheets (adopted sheets, Chrome 101+) merge into the shadow cascade without touching the light DOM. Inline styles on the host element still beat shadow !important declarations, which trips up Web Component developers who expect encapsulation to be total.

Q: How do CSS Custom Properties interact with cascade?
A: Custom properties cascade just like any other declaration. --color: red wins based on normal cascade rules. The substitution color: var(--color) happens after cascade resolves the variable's winning value, not before.

Examples

Specificity conflict with merged properties

html
<!DOCTYPE html> <html> <head> <style> p { color: blue; font-size: 14px; } /* tag selector, specificity 0,0,0,1 */ .highlight { color: red; } /* class selector, specificity 0,0,1,0 */ </style> </head> <body> <!-- color: red from .highlight, font-size: 14px from p (no conflict there) --> <p class="highlight">Red text, 14px font.</p> </body> </html>

.highlight wins color. But font-size from p still applies because nothing else declares it. Both rules contribute to the final computed style. That is the merge in action.

Cascade in a React and Tailwind setup

jsx
/* App.css */ .button { background: gray; padding: 8px; border-radius: 4px; } /* Tailwind generates: */ /* .bg-blue-500 { background-color: rgb(59 130 246); } */ function Button({ primary }) { return ( <button className={`button ${primary ? 'bg-blue-500' : 'bg-gray-500'}`}> Click me </button> ); } /* Result for a primary button: background: rgb(59 130 246) from Tailwind (same specificity, loads later, wins) padding: 8px from .button (no conflict, merges in) border-radius: 4px from .button (no conflict, merges in) */

Both .button and .bg-blue-500 are class selectors with equal specificity. Tailwind wins background by source order because its generated stylesheet loads after yours. The padding and border-radius from .button merge in because Tailwind never declares them. This is the standard Tailwind pattern in Next.js apps.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?