Use @layer to manage CSS cascade order explicitly
CSS Cascade Layers (@layer) are used to give the codebase explicit, predictable control over specificity and cascade order, eliminating the need to fight specificity with !important.
- Declare your layer stack at the top of your main CSS file to fix order regardless of import sequence
- Styles in later layers win over styles in earlier layers, regardless of selector specificity
- Put third-party/reset styles in early layers so your own styles always override them
- Unlayered styles (no @layer) have the highest priority — migrate gradually
Rule Details
CSS Cascade Layers (opens in new tab) give you a new dimension of cascade control that sits above specificity. The web.dev guide (opens in new tab) is a good mental model here: instead of relying on selector weight and source-order accidents, you define an explicit priority stack.
Code Example
Higher priority (wins)
↑
│ Unlayered styles (implicit top layer)
│ utilities layer
│ components layer
│ base layer
│ reset layer
↓
Lower priorityStyles in a higher layer win over styles in lower layers regardless of selector specificity. A single class in the utilities layer beats a three-class selector in the base layer.
Why It Matters
Specificity conflicts are one of the most common causes of CSS bugs in large codebases. Developers resort to !important, deeply nested selectors, or inline styles to win specificity wars, creating an unmaintainable arms race. Cascade layers move the source of truth for cascade order from selector specificity to explicit layer ordering, making the system predictable and easy to reason about.
Declaring the Layer Stack
Always declare the layer order at the top of your CSS entry point. The order of @layer declarations determines priority — later = higher priority:
/* main.css — declare the full stack first */
@layer reset, base, components, utilities;
/* Then import or define each layer */
@import url('./reset.css') layer(reset);
@import url('./base.css') layer(base);
@import url('./components.css') layer(components);
@import url('./utilities.css') layer(utilities);If you do not pre-declare layers, their order is determined by when they are first encountered in the source — which can change unpredictably as you add imports.
Defining Styles in a Layer
/* reset.css */
@layer reset {
*, *::before, *::after { box-sizing: border-box; }
body, h1, h2, h3, p, ul { margin: 0; padding: 0; }
img { max-width: 100%; display: block; }
}
/* base.css */
@layer base {
body {
font-family: var(--font-sans);
font-size: 1rem;
line-height: 1.5;
color: var(--color-text);
background-color: var(--color-bg);
}
h1 { font-size: clamp(1.75rem, 4vw, 2.5rem); }
h2 { font-size: clamp(1.375rem, 3vw, 1.875rem); }
a { color: var(--color-link); }
}
/* components.css */
@layer components {
.btn {
display: inline-flex;
align-items: center;
padding: 0.5em 1.25em;
border-radius: 0.375rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.15s;
}
.btn-primary {
background-color: var(--color-primary);
color: white;
}
}
/* utilities.css */
@layer utilities {
.sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
}
.mt-4 { margin-top: 1rem; }
.flex { display: flex; }
.gap-2 { gap: 0.5rem; }
}Third-Party Styles in Layers
Wrapping third-party CSS in a low-priority layer ensures your own styles always win — no more !important battles with third-party components:
/* Wrap a third-party library in a layer */
@import url('third-party-ui/styles.css') layer(vendor);
/* Declare vendor below your own layers — your styles always win */
@layer vendor, reset, base, components, utilities;/* Or wrap inline */
@layer vendor {
@import url('some-component-library/dist/styles.css');
}Nested Layers
Layers can be nested for more granular control:
@layer components {
@layer forms {
.input { border: 1px solid var(--color-border); }
}
@layer cards {
.card { border-radius: 0.5rem; }
}
}
/* Reference nested layers as components.forms, components.cards */Migrating an Existing Codebase
A safe migration strategy — move styles into layers without rewriting everything at once:
/* Step 1: Declare layers but keep existing styles outside (they stay highest priority) */
@layer reset, base, components;
/* Step 2: Move resets into the reset layer first */
@layer reset {
/* existing reset styles */
}
/* Step 3: Move base styles */
@layer base {
/* existing base styles */
}
/* Step 4: Move component styles — existing utility/override styles remain unlayered */
@layer components {
/* existing component styles */
}
/* Step 5: Move utilities last — these become the new top layer */
@layer utilities {
/* existing utility styles */
}@layer and Tailwind CSS
Tailwind v3.3+ supports cascade layers natively. Configure it in tailwind.config.js:
// tailwind.config.js
module.exports = {
future: {
hoverOnlyWhenSupported: true,
},
}/* With @import for Tailwind's own layer registration */
@layer base {
@tailwind base;
}
@layer components {
@tailwind components;
}
@layer utilities {
@tailwind utilities;
}Browser Support
@layer (opens in new tab) is supported in all modern browsers (Chrome 99+, Firefox 97+, Safari 15.4+). The Chrome rollout notes (opens in new tab) are a useful reminder that unsupported browsers simply treat the rules as unlayered styles, so compatibility is mostly about whether your project still targets those engines.
Any style defined outside a @layer block has higher priority than all layered styles. This is intentional for migration (existing styles keep working) but can cause confusion if you mix layered and unlayered styles. Establish a team convention: either put everything in layers, or reserve unlayered styles only for critical emergency overrides.
Verification
Use the MDN @layer reference (opens in new tab) while checking DevTools so you are verifying the declared layer order and not just the final visual result.
- Open DevTools → Elements → Styles panel and verify that layer names appear in the style source (e.g.,
layer(components)or@layer components). - Search the codebase for
!importantdeclarations — each one is a candidate for elimination via proper layer ordering. - Verify the layer stack is declared at the top of the main CSS entry file, before any
@importstatements that use those layers. - Confirm that third-party CSS imports are wrapped in a
vendororthird-partylayer declared before your own layers.
Use with AI
Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.
Check
Verify implementation
Check whether the CSS uses cascade layers to manage specificity, and whether the layer order is explicitly declared.
Fix
Auto-fix issues
Introduce @layer declarations for reset, base, components, and utilities, and migrate existing styles into appropriate layers.
Explain
Learn more
Explain how CSS Cascade Layers work, how layer order determines cascade priority, and why this is preferable to specificity-based overrides.
Review
Code review
Review the CSS for !important declarations, excessively specific selectors (more than two class selectors), and inline styles used to override component styles — all are signals that the cascade is not under control.