CSS cascade layers are the ultimate tool to win the specificity wars. Used alongside the :where selector, specificity problems are a thing of the past.
Or so I thought. Turns out cascade layers are leakier than a xenonite sieve. Cross-layer shenanigans can make bad CSS even badder. I discovered a whole new level of specificity hell. Scroll down if you dare! There are advantages too, Iāll start with a neat trick.
Neat trick
To setup this trick Iāll quickly cover my favoured CSS methodology for a small website. I find defining three cascade layers is plenty.
@layer base, components, utility;In base I add my reset styles, custom properties, anything that touches a global element, etc. In components I add the core of the website. In utility I add classes that look suspiciously like Tailwind, for pragmatic use. Visually-hidden is a utility class in my system.
Figma betrayel
I recently built a design where many headings and UI elements used an alternate font with a unique style. It made practical sense to use a utility class like the one below.
@layer utility {
.font-brand {
font-family: "Garish Sans";
letter-spacing: 20%;
text-transform: uppercase;
}
}@layer Iām just doing that here for clarity.This is but a tribute, the real class had more properties. The class is DRY and easily integrated into templates and content editors.
Adding this to the highest cascade layer makes sense. I donāt have to worry about juggling source order or overriding properties on the class itself. I especially do not have to care about specificity or slap !important everywhere like a fool.
This worked well. Then I zoom further into the Figma picture and was betrayed!
Escape hatch
The design had an edge case where letter-spacing varied for one specific component. It made sense for the design. It did not make sense for my system.
If you remember, my utility cascade layer takes priority over my components layer so I canāt simply apply a unique style to the component.
@layer base, components, utility;For the sake of a demo letās assume my component has this markup.
<div class="Component">
<h2 class="font-brand">Heading whatever</h2>
</div>I want to change back to the normal letter-spacing.
@layer components {
.Component h2 {
letter-spacing: normal;
}
}Oops, Iāve lost the specificity war regardless of what selector I use. The font-brand utility class wins because I set it up to win.
My āescape hatchā uses custom property fallback values.
@layer utility {
.font-brand {
font-family: "Garish Sans";
letter-spacing: var(--letter-spacing, 20%);
text-transform: uppercase;
}
}In most cases --letter-spacing is not defined and the default 20% is applied.
For my edge case component I can āconfigureā the utility class.
@layer components {
.Component {
--letter-spacing: normal;
}
}Iāve found this to be an effective solution that feels logical and intuitive. Iām working with the cascade. Itās a good thing that custom properties are not locked within cascade layers! I donāt think anyone would expect that to happen.
Important breach
In drafting this post I was going to use an example to show the power of cascade layers.
@layer components {
.Component h2 {
letter-spacing: normal !important;
}
}I was going to say that not even !important wins. Then I tested my example and found that !important does actually override higher cascade layers. It breaches containment too!
What colour are the paragraphs?
@layer one, two, three;
@layer one {
p { color: blue !important; }
}
@layer two {
p { color: white !important; }
}
@layer three {
p { color: black; }
}Suffice it to say that things get very weird. See my CodePen.
Spoiler: blue wins.
Iām sure there is a perfectly cromulent reason for this behaviour but on face value I donāt like it! Bleh! I feel like !important should be locked within a cascade layer. I donāt even want to talk about the inversionā¦
Iām sure there are GitHub issues, IRC logs, and cave wall paintings that discuss how cascade layers should handle !important ā they got it wrong! The fools! We could have had something good here! Okay, maybe Iām being dramatic. Iām missing the big picture, is there a real reason it has to work this way? It just feels⦠wrong? Iāve never seen a use case for !important that wasnāt tear-inducing technical debt.
Permeating layers with !important feels wrong even though custom properties behaving similar feels right. Itās hard to explain. I reckon if youāve built enough websites youāll get that sense too? Or am I just talking nonsense?
I subscribe to the dogma that says !important should never be used but itās not always my choice. I build a lot of bespoke themes. The WordPress + plugin ecosystem is the ultimate specificity war. WordPress core laughs in the face of āCSS methodologyā and loves to put styles where they donāt belong. Plugin authors are forced to write even gnarlier selectors. When I finally get to play, styles are an unmitigated disaster.
Cascade layers can curtail unruly WordPress plugins but if they use !important itās game over; Iām back to writing even worse code.
Update for 16 Apr 2026
Thinking about this more, it makes sense from the perspective of giving users the final word, and browsers having a spec-defined way to achieve that.
Practically though, for developers, even when everyone is on the same team a single !important can ruin the party. In scenarios like WordPress these problems manifest as more than just gnarly selectors. Bad and unmaintainable CSS snowballs into bugs and broken dreams.
In short, it might make sense in theory but in practice the evidence suggests otherwise. I remain unconvinced it needed to be this way.
Thanks for reading! Follow me on Mastodon and Bluesky. Subscribe to my Blog and Notes or Combined feeds.