Frontend Errors Cascade Conflicts
This skill diagnoses and fixes cascade conflicts in CSS : "why is my rule not winning". It assumes familiarity with @layer and @scope syntax (covered by [[frontend-syntax-css-cascade-layers-scope]]) and focuses on the decision tree, the inversions that surprise authors, and the DevTools workflow.
Sources : MDN: Specificity (verified 2026-05-19), MDN: Cascade (verified 2026-05-19), MDN: !important (verified 2026-05-19), MDN: @layer (verified 2026-05-19).
Quick Reference
Cascade sort order (FULL chain)
Apply these in order; the first DIFFERENCE decides the winner :
- Relevance. Filter to rules whose selector matches AND whose media context matches.
- Origin + Importance. See the 8-step ladder below.
- Layers. Layer order within the same origin + importance.
- Specificity. Higher wins.
- Scoping Proximity. For
@scoperules, the closest scope root wins. - Order of Appearance. Later declaration wins.
Origin + Importance ladder (low to high)
Per MDN: Cascade (verified 2026-05-19) :
| Rank | Origin | Importance |
|---|---|---|
| 1 (lowest) | user-agent | normal |
| 2 | user | normal |
| 3 | author | normal |
| 4 | CSS keyframe animations | n/a |
| 5 | author | !important |
| 6 | user | !important |
| 7 | user-agent | !important |
| 8 (highest) | CSS transitions | n/a |
KEY INSIGHT : !important REVERSES origin order, so a user !important beats an author !important. This is how user stylesheets for accessibility (high-contrast, large-text) win against author code.
Layer order for NORMAL author declarations
Per MDN: @layer (verified 2026-05-19), within the author origin, normal :
| Rank | Source |
|---|---|
| 1 (lowest) | First-declared layer |
| 2 | Second-declared layer |
| ... | ... |
| N | Last-declared layer |
| N+1 (highest) | Unlayered styles |
UNLAYERED ALWAYS WINS for normal declarations. Mixing unlayered and layered CSS by accident is the #1 source of "my rule does not override" bugs.
Layer order for !important author declarations (REVERSED)
| Rank | Source |
|---|---|
| 1 (lowest) | Unlayered !important |
| 2 | Last-declared layer !important |
| ... | ... |
| N-1 | Second-declared layer !important |
| N (highest) | First-declared layer !important |
REVERSAL : !important in the FIRST layer wins. This lets a base reset layer win the !important race even though it loses the NORMAL race.
Specificity ladder
Per MDN: Specificity (verified 2026-05-19). Three columns ; left-to-right comparison ; first column with a difference wins.
| Column | Counts | Examples |
|---|---|---|
| ID (1-0-0) | ID selectors | #hero |
| CLASS (0-1-0) | class, attribute, pseudo-class | .btn, [type="radio"], :hover, :nth-of-type(3n) |
| TYPE (0-0-1) | type, pseudo-element | p, h1, ::before, ::placeholder |
| (0-0-0) | universal, :where() |
*, :where(...) |
Inline style attribute acts as a higher tier (overrides all selectors) but is itself overridden by any !important from any selector.
The four pseudo-class rules
| Pseudo | Specificity contribution |
|---|---|
:where(<list>) |
Always 0-0-0. The arguments DO NOT add weight. |
:is(<list>) |
The HIGHEST specificity among the arguments. |
:not(<list>) |
The HIGHEST specificity among the arguments. |
:has(<list>) |
The HIGHEST specificity among the arguments. |
These rules are the source of two common surprises : :where(.a #b) is 0-0-0 (the inner #b does NOT contribute) ; :is(.a, #b) is 1-0-0 (the #b wins).
Cascade-control keywords
| Keyword | Effect |
|---|---|
inherit |
Take the parent's computed value. |
initial |
Reset to the property's spec-defined initial value (often NOT what you visually expect; e.g. color: initial is usually black). |
unset |
Act as inherit if inherited, otherwise initial. |
revert |
Roll back to the cascaded value from the previous origin (user-agent, typically). |
revert-layer |
Roll back to the cascaded value from the previous layer. |
Decision Trees
Decision : "Why is my rule not winning?"
Run through this in order ; STOP at the first difference :
1. Does the selector actually match the element in this state?
-> Open DevTools, inspect the element. If your rule is NOT
in the Styles panel at all, the selector is wrong.
2. Is a higher-origin or higher-importance rule winning?
-> A user-agent !important or a user !important beats your
author rule (rare, but check). DevTools shows origin.
3. Is your rule unlayered while the rule overriding it is unlayered too?
-> If both unlayered, this step does not decide. Continue.
-> If yours is layered and the OTHER is unlayered, the OTHER wins
for normal declarations. Move your rule to unlayered OR move
the other into a layer above yours.
4. Layers : is the winning rule in a LATER-declared layer (for normal)
or an EARLIER-declared layer (for !important)?
-> Reorder the @layer declarations OR move your rule to the
winning layer.
5. Specificity : higher wins.
-> Compare the three-column specificity. If yours is lower,
wrap the conflicting rule in :where() to ZERO its
specificity OR add a matching higher-specificity selector.
6. Scoping proximity (@scope only) : the closest scope-root wins.
-> If both are inside @scope, the one whose root is fewer DOM
hops away wins. This overrides source order.
7. Source order : later declaration wins.
-> Move your rule to AFTER the conflicting rule, or refactor
so both are in the same layer at the right order.
Decision : "How do I undo a specificity war?"
Symptom : the codebase has !important everywhere AND ID selectors
AND nested-class chains. New rules require !important to win.
Step 1 : Stop adding !important. It is a symptom; adding more
makes the problem worse.
Step 2 : Wrap the offending selectors in :where(...) to ZERO their
specificity. For example,
#sidebar .menu .item { ... }
becomes
:where(#sidebar) .menu .item { ... } /* now 0-1-1 */
Step 3 : Move the high-specificity rules into an EARLIER cascade
layer. Then any author code in a LATER layer wins for
normal declarations regardless of specificity inside the
earlier layer.
Step 4 : Audit and remove ID selectors. Replace with class or
data-attribute selectors :
#sidebar -> [data-region="sidebar"] /* 0-1-0 */
Step 5 : Move ALL author CSS into named layers. Unlayered CSS
beats layered, so any straggler unlayered rule will
silently win. Layering everything makes the cascade
predictable.
Decision : "When is !important acceptable?"
Almost never. The genuinely acceptable cases :
Utility-class layer in a design system that INTENTIONALLY
beats component rules. Tailwind-style "u-text-center" classes
living in a `utilities` layer with !important declarations
ensure they always override components, no matter what.
-> Acceptable. Document the contract.
Print stylesheet overrides that MUST defeat all screen styles.
-> Acceptable when truly necessary.
Third-party CSS injected outside your control that you cannot
refactor.
-> Last resort. Better: put the third-party in an EARLIER
cascade layer so your code wins via layer order.
Everywhere else : NEVER. !important is a sign that layering
discipline is missing.
Patterns
Pattern 1 : DevTools workflow for diagnosing a cascade conflict
- In Chrome DevTools, select the element with Elements -> click.
- Open the Styles panel.
- Read top-down ; rules are sorted in cascade order (winning rules at top).
- A struck-through declaration means it lost the cascade ; the rule on the line ABOVE won.
- Hover over the rule to see which file and line declared it.
- Click the small badge "Layer : " next to the rule to see its layer assignment ; an absent badge means unlayered.
- Switch to the Computed tab to see the final value AND the chain of declarations that produced it (click a property to expand).
- If the rule is missing entirely, the selector did not match : double-check the element's classes and the selector syntax.
Pattern 2 : Lower specificity by wrapping in :where()
/* Before : 1-2-1 */
#app .panel .header h2 { font-size: 1.5rem; }
/* After : 0-2-1 */
:where(#app) .panel .header h2 { font-size: 1.5rem; }
/* After (stronger) : 0-0-1 */
:where(#app .panel .header) h2 { font-size: 1.5rem; }
:where(...) contributes 0-0-0 and zeros everything inside. Use it on framework / scaffold selectors that the design system should override.
Pattern 3 : Layer order that resists author drift
/* Declare ALL layers up front so order is fixed */
@layer reset, vendor, base, theme, components, utilities;
@layer reset { /* CSS reset */ }
@layer vendor { /* Third-party CSS imported into the vendor layer */ }
@layer base { /* design-system base */ }
@layer theme { /* tokens */ }
@layer components { /* component styles */ }
@layer utilities { /* utility classes (intentionally win via !important if needed) */ }
Every project rule MUST live in one of these layers ; nothing unlayered. Unlayered author CSS would silently beat everything.
Pattern 4 : Importing third-party into a layer
@import url("third-party.css") layer(vendor);
The third-party CSS now sits in the vendor layer. Your components / utilities layers (declared AFTER vendor) win for normal declarations regardless of the specificity inside third-party.css.
Pattern 5 : Using revert-layer to escape a vendor override
@layer vendor {
.btn { padding: 1rem; border-radius: 8px; }
}
@layer components {
.btn { padding: revert-layer; } /* roll back to vendor's padding value... wait, no */
}
revert-layer rolls back to the cascaded value from the PREVIOUS LAYER. The above example actually keeps the vendor padding. To roll back to user-agent default, use revert. Pattern is most useful inside a heavily-layered system where you want to "undo" a specific layer's contribution.
Anti-Patterns Index
See anti-patterns.md. Eight cataloged : mixing unlayered + layered CSS expecting source order; !important chain to win specificity; assuming !important follows the same layer order as normal; :where(:not(.x)) .y surprise zero specificity; :is(.a, #b) surprise ID-level specificity; ID-based styling creating untouchable rules; @scope rule expected to lose to deeper-DOM normal rule; removing a class hoping a rule disappears.
Reference Links
- Methods and signatures : full cascade ladder, specificity examples, layer rules including the
!importantinversion, scope proximity rule, cascade-control keywords. - Examples : DevTools workflow walkthroughs and code examples for each cascade-conflict resolution pattern.
- Anti-patterns : eight cataloged anti-patterns with symptom, root cause, and fix.
Cross-references
[[frontend-syntax-css-cascade-layers-scope]]:@layerand@scopesyntax details.[[frontend-syntax-css-nesting-logical-properties]]: native nesting specificity rules.[[frontend-syntax-css-has-selector]]::has()specificity and performance considerations.