david kimani
Work with me

Cascade layers only work if you declare the order first

A class selector in my own layer was losing to a type selector in a framework reset. The layer order I had carefully declared was being ignored, and nothing said so.

A rule that could not lose, losing

I had a button styled like this:

css
@layer app {
  .btn--accept {
    background-color: var(--accent);
    color: var(--on-accent);
  }
}

The background was right. The text colour was not. Same rule, same block, one property applying and the other not.

The winner turned out to be this, from a framework reset:

css
@layer framework-core.reset {
  button { color: inherit }
}

A type selector beating a class selector. Which is correct, if that layer sorts after mine.

Position is fixed at first mention

A cascade layer’s position is decided the first time its name appears, and nothing later can move it. A statement like this sets the order:

css
@layer framework-core, framework-utilities, app, framework-final;

but only if the browser reads it before it has met any of those names. If app has already been seen, that statement cannot pull it earlier. It appends the names it has not seen yet, behind the ones it has.

My ordering file said exactly the right thing. It was imported from a plugin, and the global stylesheet that declares app was emitted before it, so by the time the browser read my statement, app was already at the front and every framework layer landed behind it. The declared order and the real order were reverses of each other, and nothing reported it.

The fix is boring

Load the ordering file first. Not first among your own stylesheets. First, before anything that might declare a layer.

For me that meant putting it at the top of the global CSS array as well as leaving the plugin import alone. A repeated layer statement naming layers that already exist is a no-op, so having it twice costs nothing and keeps the order right if somebody reshuffles that array later.

How to see the real order

The declared order is not the thing to inspect. The emitted order is. In the browser:

js
[...document.styleSheets]
  .flatMap((s) => { try { return [...s.cssRules] } catch { return [] } })
  .filter((r) => r.constructor.name === 'CSSLayerStatementRule')
  .map((r) => [...r.nameList])

Or read the built CSS and find which layer block appears first. If your layer is not where you assumed, every specificity argument you have had with that framework was settled before you started.

What I take from it

Layers are not a way to win. They are a way to declare who wins, and the declaration has to arrive before the contestants do.

Share
XLinkedIn

Enjoyed this post? Subscribe for more. No noise, unsubscribe anytime.

Related posts