CSS Cascade Layers Guide
Master CSS Cascade Layers (@layer) to control specificity, organize your stylesheets, and manage style precedence without fighting specificity wars. Finally, a native solution for managing CSS architecture at scale.
What Are Cascade Layers?
CSS Cascade Layers introduce a new way to organize and control the cascade by creating explicit layers of styles. Layers allow you to group related styles and control their precedence in a more maintainable way than relying solely on specificity.
Why use cascade layers?
- ✅ Manage specificity conflicts across different style sources
- ✅ Organize CSS by concern (reset, base, theme, components, utilities)
- ✅ Override framework styles without
!important - ✅ Better separation between third-party and custom CSS
- ✅ More maintainable and predictable cascade behavior
Normalize browser defaults
Typography, colors, global styles
Design system tokens, variables
Component-specific styles
Utility classes, overrides
Basic Syntax
The @layer at-rule is used to declare and populate cascade layers.
Creating a Layer
/* Define and populate a layer */
@layer base {
h1 {
font-size: 2rem;
margin-bottom: 1rem;
}
p {
line-height: 1.6;
}
}
/* Add more styles to the same layer */
@layer base {
a {
color: blue;
text-decoration: none;
}
}
Declaring Layer Order First
/* Declare layers upfront to set order */
@layer reset, base, theme, components, utilities;
/* Later, populate the layers */
@layer reset {
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
}
@layer utilities {
.text-center { text-align: center; }
.hidden { display: none; }
}
Best practice: Declare your layers at the top of your stylesheet to establish the order explicitly.
Layer Ordering & Priority
Layers are applied in the order they are declared. Later layers have higher priority, regardless of specificity within each layer.
Lowest Priority
Medium Priority
Highest Priority
/* Declare order: framework comes first, custom comes last */
@layer framework, custom;
@layer framework {
/* Even with high specificity, this loses to custom layer */
button.btn.primary {
background: blue;
color: white;
padding: 0.5rem 1rem;
}
}
@layer custom {
/* Simple selector, but wins because it's in a later layer */
button {
background: green;
}
}
/* Result: button will be GREEN, not blue */
Later layer wins, regardless of specificity!
Key Concept: Layer order is more important than specificity. A simple selector in a later layer will override a highly specific selector in an earlier layer.
Named Layers
Give your layers descriptive names to make your CSS architecture clear and maintainable.
/* Common layer naming patterns */
@layer reset, base, tokens, layouts, components, utilities, overrides;
/* Reset layer - normalize defaults */
@layer reset {
*, *::before, *::after {
box-sizing: border-box;
}
body {
margin: 0;
line-height: 1.5;
}
}
/* Base layer - element defaults */
@layer base {
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
line-height: 1.2;
}
img {
max-width: 100%;
height: auto;
}
}
/* Tokens layer - design system variables */
@layer tokens {
:root {
--color-primary: #667eea;
--color-secondary: #764ba2;
--spacing-unit: 1rem;
}
}
/* Components layer - component styles */
@layer components {
.card {
background: white;
border-radius: 8px;
padding: var(--spacing-unit);
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.button {
background: var(--color-primary);
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
}
}
/* Utilities layer - utility classes */
@layer utilities {
.text-center { text-align: center; }
.mt-4 { margin-top: 2rem; }
.flex { display: flex; }
.hidden { display: none; }
}
Anonymous Layers
Create unnamed layers when you need isolation without naming.
/* Anonymous layer - useful for temporary or isolated styles */
@layer {
.temp-style {
background: red;
}
}
/* Another anonymous layer - separate from the first */
@layer {
.another-temp {
color: blue;
}
}
/* Anonymous layers can't be referenced later */
/* They're useful for:
- Isolating third-party CSS
- Temporary overrides
- One-off encapsulation
*/
When to use anonymous layers: Use anonymous layers when you need to isolate styles but don't need to reference the layer later. Each @layer {} creates a new, unique anonymous layer.
Nested Layers
Create sub-layers within layers for more granular organization.
/* Define nested layers */
@layer framework {
@layer reset {
* {
margin: 0;
padding: 0;
}
}
@layer base {
body {
font-family: system-ui;
line-height: 1.5;
}
}
@layer theme {
:root {
--primary: #667eea;
--secondary: #764ba2;
}
}
}
/* Reference nested layers using dot notation */
@layer framework.theme {
:root {
--accent: #f093fb;
}
}
/* You can also declare nested layers upfront */
@layer components {
@layer buttons, cards, forms, modals;
}
@layer components.buttons {
.btn {
padding: 0.5rem 1rem;
border-radius: 4px;
}
}
@layer components.cards {
.card {
border-radius: 8px;
padding: 1.5rem;
}
}
Nested Layer Hierarchy Example
/* Complex nested structure */
@layer design-system {
@layer foundations {
@layer reset, typography, colors;
}
@layer patterns {
@layer layout, components, utilities;
}
}
/* Populate nested layers */
@layer design-system.foundations.reset {
*, *::before, *::after {
box-sizing: border-box;
}
}
@layer design-system.patterns.components {
.card { /* card styles */ }
.button { /* button styles */ }
}
/* Priority order (lowest to highest):
1. design-system.foundations.reset
2. design-system.foundations.typography
3. design-system.foundations.colors
4. design-system.patterns.layout
5. design-system.patterns.components
6. design-system.patterns.utilities
*/
Importing Stylesheets into Layers
Import external stylesheets directly into layers using the @import rule with the layer() function.
/* Import into named layers */
@import url('normalize.css') layer(reset);
@import url('typography.css') layer(base);
@import url('theme.css') layer(theme);
@import url('components.css') layer(components);
/* Import into anonymous layer */
@import url('third-party.css') layer();
/* Import with media queries */
@import url('print.css') layer(print) print;
/* Import multiple files into nested layers */
@import url('foundations/reset.css') layer(framework.reset);
@import url('foundations/base.css') layer(framework.base);
@import url('patterns/buttons.css') layer(framework.patterns.buttons);
/* Declare order first, then import */
@layer framework, custom, overrides;
@import url('bootstrap.css') layer(framework);
@import url('my-styles.css') layer(custom);
@import url('final-tweaks.css') layer(overrides);
Pro tip: By importing third-party frameworks into early layers, you can easily override them with your custom styles in later layers without using !important.
Real-World Import Example
/* main.css - Entry point */
/* 1. Declare layer order */
@layer
normalize,
vendors,
base,
themes,
layouts,
components,
utilities,
overrides;
/* 2. Import external dependencies */
@import url('normalize.css') layer(normalize);
@import url('bootstrap.min.css') layer(vendors);
@import url('prism.css') layer(vendors);
/* 3. Import your own stylesheets */
@import url('base/typography.css') layer(base);
@import url('base/colors.css') layer(base);
@import url('themes/dark.css') layer(themes);
@import url('themes/light.css') layer(themes);
@import url('layouts/grid.css') layer(layouts);
@import url('layouts/flex.css') layer(layouts);
@import url('components/buttons.css') layer(components);
@import url('components/cards.css') layer(components);
@import url('components/forms.css') layer(components);
@import url('utilities/spacing.css') layer(utilities);
@import url('utilities/text.css') layer(utilities);
/* 4. Page-specific overrides */
@layer overrides {
.special-case {
/* These styles will override everything above */
}
}
Unlayered Styles
Styles not in any layer have higher priority than all layered styles (except !important).
/* Declare layers */
@layer base, components, utilities;
@layer base {
p {
color: black;
}
}
@layer utilities {
/* Even though this is in the highest layer... */
.text-blue {
color: blue !important; /* needs !important to override unlayered */
}
}
/* Unlayered styles beat all layers (without !important) */
p {
color: red; /* This wins over layered styles */
}
/* The cascade priority order:
1. Layered styles (in layer order)
2. Unlayered styles (highest priority)
3. !important in reverse order:
- Unlayered !important
- Layer !important (reversed)
*/
Unlayered styles beat all layers!
Important: Be strategic about what you leave unlayered. Unlayered styles will override all your layered styles, which can lead to unexpected results if not managed carefully.
Complete Cascade Order
Understanding the full cascade order helps you write more predictable CSS.
Styles in layers, from first to last layer declared
@layer base { ... }
Regular styles not in any layer
div { color: blue; }
Styles applied directly to elements
<div style="color: red;">
Currently animating properties
@keyframes { ... }
Important declarations not in layers
div { color: blue !important; }
Important in layers, last to first
@layer base { color: red !important; }
Inline important declarations (avoid!)
<div style="color: red !important;">
Currently transitioning properties
transition: color 0.3s;
Key insight: !important in layers works in reverse order. This means earlier layers' !important declarations beat later layers' !important declarations. This is intentional and allows base/reset layers to have final say when absolutely necessary.
Cascade Order Comparison Table
| Declaration | Priority | Use Case |
|---|---|---|
@layer first { ... } |
Lowest | Resets, base styles |
@layer second { ... } |
Low | Framework styles |
@layer last { ... } |
Medium | Component overrides |
.unlayered { ... } |
High | Page-specific styles |
style="..." |
Higher | Dynamic inline styles |
@layer last { ...!important } |
Higher still | Critical overrides (reversed) |
@layer first { ...!important } |
Very High | Unmovable base rules |
.class { ...!important } |
Very High | Global overrides |
style="...!important" |
Highest | Avoid if possible! |
Practical Examples
Example 1: Framework Integration
/* Managing third-party frameworks with layers */
@layer reset, framework, custom, utilities;
/* Import Bootstrap into framework layer */
@import url('bootstrap.min.css') layer(framework);
/* Your custom component overrides */
@layer custom {
/* Simple selector beats Bootstrap's complex ones */
.btn {
border-radius: 8px;
font-weight: 600;
text-transform: none; /* Override Bootstrap's uppercase */
}
.card {
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
border: none;
}
}
/* Utility classes have final say */
@layer utilities {
.rounded-none { border-radius: 0 !important; }
.shadow-none { box-shadow: none !important; }
}
Example 2: Design System Architecture
/* Complete design system with layers */
@layer
reset,
tokens,
base,
layouts,
components,
utilities,
states;
@layer reset {
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
}
@layer tokens {
:root {
/* Colors */
--color-primary: #667eea;
--color-secondary: #764ba2;
--color-success: #4caf50;
--color-danger: #f44336;
/* Spacing */
--space-xs: 0.25rem;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 1.5rem;
--space-xl: 2rem;
/* Typography */
--font-sans: system-ui, sans-serif;
--font-mono: 'Courier New', monospace;
/* Borders */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 16px;
}
}
@layer base {
body {
font-family: var(--font-sans);
line-height: 1.6;
color: #333;
}
h1, h2, h3, h4, h5, h6 {
margin-bottom: var(--space-md);
line-height: 1.2;
}
a {
color: var(--color-primary);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
}
@layer layouts {
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--space-md);
}
.grid {
display: grid;
gap: var(--space-md);
}
.flex {
display: flex;
gap: var(--space-md);
}
}
@layer components {
.btn {
display: inline-block;
padding: var(--space-sm) var(--space-lg);
background: var(--color-primary);
color: white;
border: none;
border-radius: var(--radius-sm);
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn:hover {
opacity: 0.9;
transform: translateY(-1px);
}
.card {
background: white;
border-radius: var(--radius-md);
padding: var(--space-lg);
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.badge {
display: inline-block;
padding: var(--space-xs) var(--space-sm);
background: var(--color-primary);
color: white;
border-radius: var(--radius-sm);
font-size: 0.875rem;
}
}
@layer utilities {
.text-center { text-align: center; }
.text-right { text-align: right; }
.mt-0 { margin-top: 0; }
.mt-1 { margin-top: var(--space-sm); }
.mt-2 { margin-top: var(--space-md); }
.mt-3 { margin-top: var(--space-lg); }
.hidden { display: none; }
.sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0,0,0,0);
}
}
@layer states {
.is-loading {
opacity: 0.6;
pointer-events: none;
cursor: wait;
}
.is-disabled {
opacity: 0.5;
pointer-events: none;
cursor: not-allowed;
}
.is-active {
background: var(--color-primary);
color: white;
}
}
Example 3: Theming with Layers
/* Multi-theme system using layers */
@layer base, theme-light, theme-dark, theme-custom;
@layer base {
:root {
--transition: 0.3s ease;
}
body {
background: var(--bg);
color: var(--text);
transition: background var(--transition), color var(--transition);
}
}
@layer theme-light {
:root {
--bg: #ffffff;
--bg-secondary: #f5f5f5;
--text: #333333;
--text-muted: #666666;
--border: #e0e0e0;
}
}
@layer theme-dark {
:root[data-theme="dark"] {
--bg: #1a1a1a;
--bg-secondary: #2a2a2a;
--text: #ffffff;
--text-muted: #aaaaaa;
--border: #404040;
}
}
@layer theme-custom {
:root[data-theme="neon"] {
--bg: #000000;
--bg-secondary: #111111;
--text: #00ff00;
--text-muted: #00aa00;
--border: #00ff00;
}
:root[data-theme="retro"] {
--bg: #fdf6e3;
--bg-secondary: #eee8d5;
--text: #657b83;
--text-muted: #93a1a1;
--border: #93a1a1;
}
}
Example 4: Component Variants
/* Organize component variants with nested layers */
@layer components {
@layer button {
@layer base, sizes, variants, states;
}
}
@layer components.button.base {
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 600;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
}
@layer components.button.sizes {
.btn-sm {
padding: 0.25rem 0.75rem;
font-size: 0.875rem;
}
.btn-md {
padding: 0.5rem 1rem;
font-size: 1rem;
}
.btn-lg {
padding: 0.75rem 1.5rem;
font-size: 1.125rem;
}
}
@layer components.button.variants {
.btn-primary {
background: #667eea;
color: white;
}
.btn-secondary {
background: #764ba2;
color: white;
}
.btn-outline {
background: transparent;
border: 2px solid currentColor;
}
.btn-ghost {
background: transparent;
color: inherit;
}
}
@layer components.button.states {
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.btn:active {
transform: translateY(0);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
.btn:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
}
Example 5: Responsive Design with Layers
/* Organize responsive styles within layers */
@layer base, layouts, components;
@layer base {
:root {
--container-width: 100%;
--font-size: 16px;
--spacing: 1rem;
}
@media (min-width: 768px) {
:root {
--container-width: 720px;
--font-size: 18px;
--spacing: 1.5rem;
}
}
@media (min-width: 1024px) {
:root {
--container-width: 960px;
--spacing: 2rem;
}
}
@media (min-width: 1280px) {
:root {
--container-width: 1200px;
}
}
}
@layer layouts {
.container {
width: var(--container-width);
margin: 0 auto;
padding: 0 var(--spacing);
}
.grid {
display: grid;
gap: var(--spacing);
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.grid {
grid-template-columns: repeat(3, 1fr);
}
}
}
@layer components {
.card {
padding: var(--spacing);
}
.nav {
flex-direction: column;
}
@media (min-width: 768px) {
.nav {
flex-direction: row;
}
}
}
Browser Support
CSS Cascade Layers are supported in all modern browsers since early 2022.
✅ Supported Browsers
- Chrome 99+ (Mar 2022)
- Edge 99+ (Mar 2022)
- Firefox 97+ (Feb 2022)
- Safari 15.4+ (Mar 2022)
- Opera 85+ (Apr 2022)
🚀 Key Benefits
- Control cascade without specificity wars
- Organize CSS by architectural concern
- Override frameworks cleanly
- Better maintainability at scale
- Native browser support, no polyfill needed
Feature Detection
/* Check for @layer support */
@supports (at-rule(@layer)) {
@layer base {
/* layer styles */
}
}
/* Fallback for older browsers */
@supports not (at-rule(@layer)) {
/* traditional styles */
}
Progressive Enhancement
/* Fallback approach */
/* Default styles (work everywhere) */
.button {
background: blue;
color: white;
padding: 0.5rem 1rem;
}
/* Enhanced with layers (modern browsers) */
@supports (at-rule(@layer)) {
@layer components {
.button {
background: var(--color-primary);
border-radius: var(--radius-md);
transition: all 0.2s;
}
}
}
Migration tip: You can gradually adopt cascade layers in existing projects. Start by wrapping third-party CSS in layers, then progressively organize your own styles into layers.
Best Practices
✅ Do
- Declare layer order upfront
- Use descriptive layer names
- Import third-party CSS into layers
- Organize by concern, not specificity
- Keep unlayered styles minimal
- Document your layer strategy
❌ Don't
- Mix layered and unlayered styles
- Overuse
!importantin layers - Create too many layers (keep it simple)
- Rely on layer order without declaring it
- Use anonymous layers for reusable code
- Fight the cascade with specificity wars
Recommended Layer Structure
/* A solid foundation for most projects */
@layer
reset, /* Browser normalization */
defaults, /* Element defaults */
tokens, /* Design tokens/variables */
layouts, /* Layout patterns */
components, /* UI components */
utilities, /* Utility classes */
overrides; /* Exceptions/overrides */