The CSS cascade is the algorithm a browser uses to resolve conflicts when several declarations set the same property on the same element, so that exactly one value wins. In CSS, it sorts every competing declaration by origin and importance, context, cascade layers, specificity and finally order of appearance, and the first criterion that separates two declarations decides the outcome.
The cascade is the reason the language is called Cascading Style Sheets. It allows user agent defaults, user preferences and author styles to coexist, and it gives authors predictable tools for overriding styles without rewriting them. Understanding its exact order is the difference between deliberate styling and an escalating war of !important flags.
How the cascade sorts competing declarations
The cascade works on declarations, not rules. For each element and each property, the browser collects every declaration whose selector matches, then compares them through a fixed sequence of criteria. Only when two declarations tie on one criterion does the next criterion apply.
The sequence below reflects the current CSS Cascading and Inheritance specification, Level 5 and later. Earlier mental models that jump straight to specificity skip several steps that now matter in practice, especially cascade layers and scoping proximity.
| Step | Criterion | What wins |
|---|---|---|
| 1 | Origin and importance | The higher-ranked origin, with importance reversing the order |
| 2 | Context | Shadow DOM context rules decide between encapsulated styles |
| 3 | Element-attached styles | Declarations in the style attribute beat selector-based rules |
| 4 | Cascade layers | Later layers win for normal rules; earlier layers win for important rules |
| 5 | Specificity | The selector with the higher (ID, class, type) weight |
| 6 | Scoping proximity | The @scope root closest to the element |
| 7 | Order of appearance | The declaration that appears last in document order |
Once a winner exists, the result is the cascaded value. If no declaration matches at all, the property falls back to inheritance for inherited properties such as color and font-family, or to the initial value for non-inherited properties such as margin and border.
Origins, importance and cascade layers
Every declaration belongs to an origin: the user agent stylesheet built into the browser, the user stylesheet configured by the reader, or the author stylesheet written by the site. For normal declarations, author beats user and user beats user agent. Adding !important reverses this order, which protects user accessibility preferences from author styles.
| Rank | Origin and importance | Typical purpose |
|---|---|---|
| 1 | Transitions | Interpolated values during a running transition |
| 2 | Important user agent | Browser rules that must never be overridden |
| 3 | Important user | Forced accessibility choices such as minimum font size |
| 4 | Important author | Deliberate author overrides |
| 5 | Animations | Values produced by running keyframe animations |
| 6 | Normal author | Ordinary site stylesheets |
| 7 | Normal user | Reader preferences without importance |
| 8 | Normal user agent | Default browser styling of HTML elements |
Cascade layers, declared with @layer, divide the author origin into ordered buckets. A typical order is reset, base, components, utilities. Any declaration in a later layer beats any declaration in an earlier layer regardless of specificity, and styles outside any layer beat all layered styles. For important declarations the layer order is reversed, mirroring the logic of origins.
Layers solve a long-standing problem: third-party CSS with heavy selectors can be placed in a low layer, so that simple selectors in later layers override it without specificity hacks.
The cascade compared with specificity and inheritance
The cascade is often confused with its components. Specificity is one step inside the cascade, not a synonym for it, and inheritance only operates after the cascade finds no winner. Separating the three concepts clarifies most debugging sessions.
| Mechanism | Question it answers | When it applies |
|---|---|---|
| Cascade | Which declaration wins among all candidates | Whenever two or more declarations match |
| Specificity | Which selector is more targeted | Only after origin, context and layers tie |
| Inheritance | What value a child takes from its parent | Only when no declaration matches the property |
| CSS variables | What value a custom property holds | Custom properties cascade first, then substitute |
| Media and container queries | Whether a rule applies at all | Before the cascade, by filtering rules |
Custom properties deserve a note: a CSS variable is itself cascaded like any property, and the winning value is then substituted where var() is used. Media queries and container queries do not rank declarations; they decide whether a rule enters the cascade in the first place.
Controlling the cascade in practice
Good architecture uses the cascade deliberately instead of fighting it. The most robust approach in modern CSS combines explicit layer order, low and uniform specificity and the CSS-wide keywords that reset values on purpose.
| Situation | Recommended tool | Reason |
|---|---|---|
| Third-party library overrides | Place the library in an early @layer | Your styles win without heavier selectors |
| Component needs a local reset | all: revert-layer or revert | Rolls back to the previous layer or origin |
| Avoiding specificity growth | :where() around selectors | Contributes zero specificity |
| Raising one selector slightly | :is() with a stronger argument | Takes the highest argument specificity |
| True one-off utility override | !important inside a utilities layer | Contained and predictable |
The keywords initial, inherit, unset, revert and revert-layer act on cascade results. `revert` returns the property to the value of the previous origin, usually the browser default, while initial jumps to the specification default, which for display is inline rather than the expected block.
Common cascade mistakes and their causes
Most styling bugs that seem random are deterministic cascade outcomes. Browser developer tools list every matching declaration and strike through the losers, which is the fastest way to see which criterion decided the result.
- Escalating `!important`: each override needs another one, and the order eventually reverses unexpectedly inside layers.
- Assuming source order beats specificity: order of appearance applies only after specificity ties.
- Unlayered styles overriding layers: any rule outside
@layerbeats every layered rule, which surprises teams that adopt layers gradually. - Inline styles winning silently: JavaScript that writes to the style attribute outranks stylesheet selectors.
- Expecting inheritance on non-inherited properties: padding or border never inherit unless
inheritis set explicitly.
A plain-language summary
Imagine several people giving instructions about the same thing, such as the color of a heading. The browser cannot follow all of them, so it uses a fixed ranking system to choose one. First it asks who gave the instruction and how strongly it was marked, then which group it belongs to, then how precisely it points at the element, and finally which came last.
That ranking system is the CSS cascade. Once a designer understands its order, styles stop looking random: every winning value has a clear, traceable reason, and fixing a wrong color becomes a matter of reading the ranking rather than guessing.
An example from practice
A product team integrates a date-picker library whose stylesheet uses selectors such as .dp-wrapper .dp-table td.dp-day. Their own button styles use single classes, so the library wins every conflict, and developers start adding !important to dozens of rules.
The team instead declares @layer reset, vendor, base, components, utilities; at the top of the main stylesheet and imports the library into the vendor layer. Because layer order is checked before specificity, a plain .day rule in the components layer now overrides the library’s four-part selector.
Within one sprint they remove more than forty `!important` flags, and new components no longer need specificity tricks. The outcome is a predictable stylesheet where overrides follow the declared layer order instead of selector length.