# Cascade layers and `!important` in WordPress themes

## Background: the two rules that drive everything

1. Normal declarations: unlayered CSS beats layered CSS, at any specificity. A [0,0,0] unlayered rule beats a [0,4,0] rule inside @layer components.
2. Important declarations: layer order reverses. Earlier layers beat later layers, and unlayered `!important` ranks last.

**Rule 1** is why theme styles in a layer lose to core. **Rule 2** is why they win when both sides use `!important`, and it is the part that makes "just put core in a layer" backfire.

## Drawbacks of pulling core styles into a layer

Coverage is incomplete by construction. Core emits CSS through at least three paths, and they are not equally reachable:

- Inline styles registered through wp_styles(), such as global-styles. Filterable, reliable.
- Enqueued files such as wp-block-library. Reachable only by converting the <link> into an @import ... layer(), or by inlining the file.
- Raw `<style>` blocks echoed with no filter, such as wp_enqueue_block_support_styles, which prints to wp_footer on classic themes. Reachable only by buffering page output.

You never end up with "core is layered." You end up with a partially layered cascade, which is harder to reason about than a fully unlayered one because the rules differ by source.

The `!important` inversion works against you. Moving core into a layer declared before your own hands core's `!important` declarations priority over yours. Core uses `!important` for its layout guarantees,
so this breaks real overrides. The fix is a dedicated first-declared overrides layer used only for important rules, which means the project now has two special layers whose semantics every developer
must hold in their head.

Failures are silent and delayed. Nothing errors when a core update, a plugin, or a new inline style introduces an unlayered source. The regression surfaces as visual drift, often on pages nobody is
actively reviewing.

Layer order depends on document order unless you force it. The order is fixed by where each layer name first appears. Build tooling often prints theme CSS outside wp_styles, so document order is not
fully under your control without an explicit @layer statement emitted early.

The editor canvas is a second cascade. The block editor loads styles through a different path than the front end. Any layering has to be mirrored there or the editor misrepresents the result, which
is worse than the original problem because authors make decisions from it.

File-based core CSS costs render time. @import is render-blocking and serializes loading. Inlining avoids that but bloats the document and forfeits caching.

The maintenance surface scales with the project, not the fix. Every plugin that emits CSS is another wrap to write and another thing to re-verify on update. Across a portfolio of sites this
compounds.

It is theme-specific knowledge with no external documentation. A custom cascade architecture is not transferable between projects or developers, and nothing in the WordPress docs describes it.

It does not eliminate `!important` anyway. Core's own important declarations still require important declarations to beat, or an overrides layer, which is the same complexity wearing a different hat.

## The trap worth naming explicitly

**Core deliberately wraps much of its generated CSS in :where() to give it zero specificity**, precisely so themes can override it with ordinary selectors. Putting theme styles in a cascade layer
forfeits the win core intended you to have. The conflict is created by the layer, not by core's specificity.

Practical consequence: layering is most likely to hurt exactly where core tried hardest to be polite.

---

## Why `!important` is sometimes the lower-friction correct answer

It is local, legible, and reviewable. One declaration at the point of conflict, with one comment naming what it beats. A reviewer can evaluate it without understanding a global architecture.

**Against core's own `!important`, nothing else works**. Within the same origin, an important declaration can only be beaten by another important declaration. Specificity does not enter into it. Where core
uses `!important` for layout guarantees, `!important` is not a shortcut, it is the only instrument.

Its cost is bounded and predictable. The blast radius is one property on one selector. A cascade architecture's cost is unbounded and global.

It fails loudly and immediately. If it does not work, you see it at the rule you just wrote, in the session you wrote it. Layer regressions surface weeks later somewhere unrelated.

The alternatives are usually worse. Specificity escalation (.block.block, long descendant chains) is harder to read, harder to override later, and moves the problem rather than solving it.

The real anti-pattern is undisciplined `!important`, not `!important` itself. The reputation comes from codebases that use it to paper over their own specificity conflicts, which is a different failure
from using it at a boundary with CSS you do not control.

## Rules of thumb to adopt

1. Reach for `!important` only against CSS you do not own: core, plugins, third-party embeds. Never against your own component styles.
2. Every `!important` carries a comment naming the exact rule it beats and why the beat is necessary. If you cannot name the rule, you do not yet understand the conflict.
3. Before using it, check whether the opposing rule is `!important`. If it is not, prefer specificity, or place the rule outside your layers, and skip the flag.
4. Set custom properties without `!important` and put the flag on the declaration that consumes them. One flag then serves every variant.
5. Do not layer core wholesale. If systematic conflicts justify it, wrap only the inline global-styles output, which is filterable, has no layout `!important`, and covers the :where() generated rules
that cause most collisions. Leave file-based core CSS unlayered so existing important overrides keep working.
6. Treat any layering of third-party CSS as a documented architectural decision with a named owner, not an incidental refactor.
7. Mirror any cascade decision in the editor canvas in the same commit.
