Cascade & Specificity
The cascade is the algorithm browsers use to resolve conflicting CSS declarations. When two or more rules apply to the same element, the cascade determines which property value takes effect. Understanding this algorithm is essential for writing predictable, maintainable stylesheets.
The word "cascading" reflects how styles flow from multiple sources — user agent defaults, user stylesheets, and author stylesheets — with later sources overriding earlier ones. The cascade considers six factors in order of importance.
| 1 | /* The cascade resolves conflicts step-by-step. |
| 2 | Only one value wins per property per element. */ |
| 3 | |
| 4 | /* Both rules apply to <p class="highlight">Hello</p> */ |
| 5 | p { color: #A0A0A0; } /* specificity: 0,0,0,1 */ |
| 6 | .highlight { color: #00FF41; } /* specificity: 0,0,1,0 → wins */ |
| 7 | |
| 8 | /* The cascade determines that .highlight is more specific */ |
When specificity and origin are equal, source order decides: the last declaration in the stylesheet wins. This is the simplest tiebreaker and a common source of confusion.
Source order applies within each stylesheet, across multiple stylesheets (later <link> tags override earlier ones), and within a single declaration block (later properties override earlier ones for the same property).
| 1 | /* Source order: last identical-specificity rule wins */ |
| 2 | .button { color: blue; } |
| 3 | .button { color: red; } /* wins — comes last */ |
| 4 | |
| 5 | /* Multiple stylesheets */ |
| 6 | /* styles.css (loaded first) */ |
| 7 | .button { background: blue; } |
| 8 | |
| 9 | /* theme.css (loaded second — overrides) */ |
| 10 | .button { background: green; } /* wins */ |
| 11 | |
| 12 | /* Inside a declaration block — later override earlier */ |
| 13 | .element { |
| 14 | color: red; |
| 15 | color: blue; /* wins — also acts as a fallback for older browsers */ |
| 16 | } |
info
Specificity is calculated as a four-column weight: (inline, IDs, classes, elements). Each selector type contributes to one column, and the comparison is made left-to-right — a single ID beats any number of classes.
| Selector | Inline | ID | Class | Element | Total |
|---|---|---|---|---|---|
| p | 0 | 0 | 0 | 1 | 0,0,0,1 |
| .card | 0 | 0 | 1 | 0 | 0,0,1,0 |
| #header .nav a | 0 | 1 | 1 | 1 | 0,1,1,1 |
| ul li:first-child | 0 | 0 | 1 | 2 | 0,0,1,2 |
| style="color: red;" | 1 | 0 | 0 | 0 | 1,0,0,0 |
| #app .sidebar .nav a:hover | 0 | 1 | 3 | 1 | 0,1,3,1 |
| .card.highlighted[data-active] | 0 | 0 | 3 | 0 | 0,0,3,0 |
| 1 | /* Specificity comparison examples */ |
| 2 | |
| 3 | /* 0,0,0,1 vs 0,0,1,0 — class wins */ |
| 4 | p { color: #808080; } |
| 5 | .text { color: #00FF41; } /* wins */ |
| 6 | |
| 7 | /* 0,1,0,0 vs 0,0,3,0 — ID wins over three classes */ |
| 8 | #title { color: #FFB000; } /* wins */ |
| 9 | .page .header .title { color: red; } |
| 10 | |
| 11 | /* 0,1,1,0 vs 0,1,0,3 — class tips the scale */ |
| 12 | #nav .link { color: blue; } /* wins (0,1,1,0) */ |
| 13 | #nav a span strong { color: red; } /* loses (0,1,0,3) */ |
| 14 | |
| 15 | /* Compound selectors add weight */ |
| 16 | a[href].external:hover { |
| 17 | /* 0,0,3,0 — attribute + class + pseudo-class */ |
| 18 | } |
Adding !important to a declaration elevates its priority above all normal declarations, regardless of specificity. It is a nuclear option that breaks the cascade and should be used as a last resort.
When two declarations both have !important, the cascade falls back to specificity and source order to resolve the conflict. So !important is not absolute — it just changes the game from "normal" to "important" contexts.
| 1 | /* Without !important */ |
| 2 | #header .title { color: red; } /* 0,1,1,0 wins */ |
| 3 | .title { color: blue; } /* 0,0,1,0 loses */ |
| 4 | |
| 5 | /* With !important — the tables turn */ |
| 6 | .title { color: blue !important; } /* wins over normal IDs */ |
| 7 | #header .title { color: red; } /* loses — no !important */ |
| 8 | |
| 9 | /* Two !important declarations — specificity kicks in */ |
| 10 | .title { color: blue !important; } /* 0,0,1,0 — loses */ |
| 11 | #header .title { color: red !important; } /* 0,1,1,0 — wins */ |
| 12 | |
| 13 | /* Acceptable uses for !important */ |
| 14 | /* 1. Utility classes (Tailwind style) */ |
| 15 | .hidden { display: none !important; } |
| 16 | |
| 17 | /* 2. Debug overrides */ |
| 18 | .debug-outline * { outline: 1px solid red !important; } |
| 19 | |
| 20 | /* 3. User-defined overrides in user stylesheets */ |
| 21 | body { font-size: 18px !important; } |
warning
CSS has three origins: the browser's user-agent stylesheet, the author's stylesheet (your CSS), and the user's stylesheet (browser preferences). Each origin can have normal and important declarations. The browser resolves them in a strict priority order.
| Priority | Origin | Importance |
|---|---|---|
| 1 (highest) | User agent | !important |
| 2 | User | !important |
| 3 | Author | !important |
| 4 | Author | Normal |
| 5 | User | Normal |
| 6 (lowest) | User agent | Normal |
This means user stylesheets with !important can override author styles — a feature designed for accessibility. Users with visual impairments can force larger fonts or high-contrast colors via their browser settings.
note
Inheritance and the cascade are often confused but serve different purposes. Inheritance passes property values from parent to child elements automatically. The cascade resolves conflicts between declarations targeting the same element.
The cascade always wins over inheritance. If a parent has color: red (inherited by child) but a direct selector targets the child with color: blue, the child will be blue because the cascade applied the declaration directly to the child.
| 1 | /* Inheritance vs Cascade example */ |
| 2 | |
| 3 | body { |
| 4 | color: #E0E0E0; /* inherited by all text children */ |
| 5 | font-family: system-ui; /* inherited */ |
| 6 | } |
| 7 | |
| 8 | /* The cascade overrides inheritance for these elements */ |
| 9 | p { color: #A0A0A0; } /* <p> uses this, not body's #E0E0E0 */ |
| 10 | article { color: #808080; } /* <article> uses this instead */ |
| 11 | |
| 12 | /* But nested children inherit from their parent's cascaded value */ |
| 13 | /* <p> inside <article> inherits #808080 from article */ |
| 14 | |
| 15 | /* Cascade keywords control inheritance behavior */ |
| 16 | .force-inherit { |
| 17 | border: inherit; /* forces inheritance of border (normally not inherited) */ |
| 18 | } |
| 19 | |
| 20 | .reset-all { |
| 21 | all: initial; /* resets every property to CSS spec default */ |
| 22 | } |
Cascade layers give authors explicit control over the cascade order, bypassing specificity and source order entirely. Layers are defined with the @layer at-rule, and styles in later layers override earlier layers regardless of specificity.
| 1 | /* Define layer order — first defined = lowest priority */ |
| 2 | @layer reset, base, components, utilities; |
| 3 | |
| 4 | /* Styles in reset layer */ |
| 5 | @layer reset { |
| 6 | * { box-sizing: border-box; margin: 0; } |
| 7 | h1, h2, h3 { font-size: 1rem; font-weight: 400; } |
| 8 | } |
| 9 | |
| 10 | /* Styles in base layer */ |
| 11 | @layer base { |
| 12 | body { font-family: system-ui; color: #E0E0E0; } |
| 13 | h1 { font-size: 2rem; font-weight: 700; } /* beats reset's h1 */ |
| 14 | } |
| 15 | |
| 16 | /* Styles in components layer */ |
| 17 | @layer components { |
| 18 | .card { |
| 19 | background: #0D0D0D; |
| 20 | padding: 24px; |
| 21 | border-radius: 8px; |
| 22 | } |
| 23 | } |
| 24 | |
| 25 | /* Unlayered styles — highest priority (beat all layers) */ |
| 26 | .fix-override { |
| 27 | background: #1A1A2E !important; |
| 28 | } |
best practice