Let's be honest — we've all been in a situation where we're fighting a third-party library's CSS, adding `!important` tags like they're going out of fashion just to change a button colour. It's frustrating, messy, and frankly, a bit of a nightmare to maintain.
What if I told you that the cascade is finally growing up? With CSS Cascade Layers (`@layer`), we can now group our styles into logical buckets where the order of the layers — not just the complexity of the selector — determines who wins the specificity battle.
The Basic Syntax: Defining Your Layers
The most important thing to remember is that layer order is king; you should always declare your layer order at the very top of your main CSS file to stay in control.
/* Establish the layer hierarchy upfront */
@layer reset, base, components, utilities;
/* Rules in 'utilities' will always beat 'components' regardless of selector weight */
Once you've defined the order, you can wrap your styles in named blocks, and CSS will respect your hierarchy even if a 'base' selector is technically more specific than a 'component' selector.
@layer base {
/* High specificity, but in a 'lower' layer */
body #main-content h1.title {
color: black;
}
}
@layer components {
/* Lower specificity, but in a 'higher' layer */
h1 {
color: rebeccapurple; /* This wins! */
}
}
Organising Complex Projects
In a real-world design system, I've found it's best to separate your resets and third-party overrides into their own layers so they don't pollute your core component logic.
@layer theme {
:root {
--primary-blue: #005aff;
}
.button {
background: var(--primary-blue);
}
}
/* Overriding a library without using !important */
@layer vendor-overrides {
.external-library-widget {
border-radius: 0;
}
}
The cool bit? You can even use the `@import` rule to assign entire external stylesheets to a specific layer right at the top of your file.
/* Import an external library into a specific layer */
@import url('bootstrap.css') layer(vendors);
@layer vendors, custom;
@layer custom {
.btn {
/* This will override Bootstrap's .btn easily */
padding: 2rem;
}
}
Nested Layers and Unlayered Styles
You can go even deeper by nesting layers within each other, which is incredibly handy for complex design systems that need internal utility overrides.
@layer framework {
@layer layout {
.container { max-width: 1200px; }
}
@layer components {
.card { padding: 1rem; }
}
}
/* Accessing nested layers from outside */
@layer framework.layout {
.container { max-width: 1440px; }
}
Here is a massive 'pro move' tip: any CSS that is NOT inside a `@layer` block is automatically treated as the highest priority layer.
@layer base {
p { color: blue; }
}
/* This is unlayered, so it wins over ANY @layer rule */
p {
color: red;
}
Browser Support
The support for Cascade Layers is actually fantastic across the board. It's ready for production in modern environments, though you should always consider your project's specific user base.
- Chrome/Edge: 99+
- Firefox: 97+
- Safari: 15.4+
- Opera: 86+
If you need to support older browsers, non-supporting browsers will ignore everything inside the `@layer` block, so I'd recommend using a PostCSS polyfill like `postcss-cascade-layers` for safety.
Wrapping Up
CSS Layers are a total game changer for how we structure our stylesheets. No more specificity hacks or fighting with legacy code — just clean, predictable logic.
- Layer order is defined by the declaration order, with the last layer winning.
- Unlayered styles always have the highest priority over layered styles.
- Use @layer to safely isolate third-party library styles from your internal design system.
If you want to go deeper and learn how to build real, production-ready CSS design systems step by step, check out my full course here: CSS Design Systems Course
Got questions or want to share how you're using this? Drop me a message on LinkedIn - I always enjoy chatting about this stuff!