SuperAPI AI Directory

frontend-errors-cascade-conflicts

Frontend & websites

View repository

Updated 10-15 22:18

About this skill

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 :

  1. Relevance. Filter to rules whose selector matches AND whose media context matches.
  2. Origin + Importance. See the 8-step ladder below.
  3. Layers. Layer order within the same origin + importance.
  4. Specificity. Higher wins.
  5. Scoping Proximity. For @scope rules, the closest scope root wins.
  6. 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

  1. In Chrome DevTools, select the element with Elements -> click.
  2. Open the Styles panel.
  3. Read top-down ; rules are sorted in cascade order (winning rules at top).
  4. A struck-through declaration means it lost the cascade ; the rule on the line ABOVE won.
  5. Hover over the rule to see which file and line declared it.
  6. Click the small badge "Layer : " next to the rule to see its layer assignment ; an absent badge means unlayered.
  7. Switch to the Computed tab to see the final value AND the chain of declarations that produced it (click a property to expand).
  8. 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 !important inversion, 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]] : @layer and @scope syntax details.
  • [[frontend-syntax-css-nesting-logical-properties]] : native nesting specificity rules.
  • [[frontend-syntax-css-has-selector]] : :has() specificity and performance considerations.