Every frontend developer has experienced the specificity trap: you add a new style, it doesn't apply because another rule is more specific, so you add !important, which breaks something else, so you add another !important... The cascade becomes a minefield.
The CSS @layer rule, now baseline-supported in all major browsers and used internally by Tailwind CSS v4, is the architectural solution. Cascade layers let you define explicit ordering for groups of styles, making specificity predictable and overridable by design.
The Problem: Specificity Wars
/* design-system.css */
.button { color: white; background: blue; } /* specificity: 0-1-0 */
.button.primary { background: #2563eb; } /* specificity: 0-2-0 */
/* component.css */
.checkout-page .button { background: green; } /* specificity: 0-2-0 */
/* This wins because it has the same specificity but comes later — fragile! */
/* emergency fix */
.button { background: red !important; } /* NUCLEAR OPTION */The root problem: specificity is a property of individual selectors, and in a large codebase with multiple authors, it becomes impossible to reason about which rule will "win."
How @layer Fixes This
Cascade layers are ordered groups. A rule in a higher-priority layer wins over any rule in a lower-priority layer, regardless of specificity:
/* Declare the layer order — lower layers lose to higher layers */
@layer base, components, utilities, overrides;
@layer base {
/* Reset styles and browser defaults */
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font-family: system-ui; }
}
@layer components {
/* Design system components */
.button {
display: inline-flex;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
background: #2563eb;
color: white;
}
}
@layer utilities {
/* Single-purpose utility classes */
.bg-red { background: red; }
.text-sm { font-size: 0.875rem; }
}
@layer overrides {
/* Theme customizations that must always win */
.button { background: var(--brand-color); }
}A rule in the overrides layer beats every rule in components, even if the component rule has higher specificity. Layer order is the new trump card — and it's explicit, not accidental.
Practical Design System Architecture
Here is a recommended layer order for a Next.js + custom CSS design system:
/* app/global.css */
@import "tailwindcss"; /* Tailwind v4 internally uses @layer */
/* Establish the layer order for all styles */
@layer reset, tokens, base, components, patterns, utilities, theme;
/* Layer 1: Browser normalization */
@layer reset {
*, *::before, *::after { box-sizing: border-box; }
img, video { max-width: 100%; display: block; }
button { cursor: pointer; font: inherit; }
}
/* Layer 2: Design tokens */
@layer tokens {
:root {
--color-brand: #6366f1;
--color-brand-hover: #4f46e5;
--spacing-unit: 0.25rem;
--radius-md: 0.375rem;
--radius-lg: 0.5rem;
--font-sans: 'Outfit', system-ui, sans-serif;
}
}
/* Layer 3: HTML element defaults using tokens */
@layer base {
body {
font-family: var(--font-sans);
line-height: 1.6;
color: oklch(25% 0 0);
}
h1, h2, h3 {
line-height: 1.2;
font-weight: 700;
letter-spacing: -0.02em;
}
a {
color: var(--color-brand);
text-decoration: underline;
text-underline-offset: 3px;
}
}
/* Layer 4: Reusable components */
@layer components {
.btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border-radius: var(--radius-md);
font-weight: 500;
transition: background 0.15s, transform 0.1s;
}
.btn-primary {
background: var(--color-brand);
color: white;
}
.btn-primary:hover {
background: var(--color-brand-hover);
transform: translateY(-1px);
}
.card {
border: 1px solid oklch(90% 0 0);
border-radius: var(--radius-lg);
padding: 1.5rem;
background: white;
}
}
/* Layer 5: Layout patterns */
@layer patterns {
.container {
max-width: 72rem;
margin: 0 auto;
padding: 0 1.5rem;
}
.grid-auto { display: grid; gap: 1rem; }
.flex-center { display: flex; align-items: center; justify-content: center; }
}
/* Layer 6: Atomic utility overrides */
@layer utilities {
.sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
}
}
/* Layer 7: Per-page and per-theme customizations */
@layer theme {
/* These ALWAYS win over component defaults */
[data-theme="dark"] {
--color-brand: #818cf8;
background: oklch(15% 0 0);
color: oklch(90% 0 0);
}
.marketing-page .btn-primary {
padding: 0.75rem 2rem;
font-size: 1.125rem;
border-radius: 9999px;
}
}Integrating with Third-Party Libraries
One of the most powerful uses of @layer is wrapping third-party styles so your own styles always win:
/* Wrap third-party CSS in a low-priority layer */
@layer vendor {
@import url('https://cdn.example.com/some-library.css');
}
/* Your styles in a higher layer will always override the library */
@layer components {
/* This wins over the vendor layer even with lower specificity */
.button { border-radius: var(--radius-md); }
}This eliminates the need for !important when overriding third-party libraries.
How Tailwind CSS v4 Uses @layer
Tailwind v4 uses @layer internally to organize its own styles:
/* What Tailwind v4 generates internally */
@layer theme { /* CSS custom properties */ }
@layer base { /* Preflight reset */ }
@layer components { /* @apply-based utilities */ }
@layer utilities { /* Generated utility classes */ }Because Tailwind v4's utilities are in the utilities layer, you can add your own styles in layers that are ordered relative to Tailwind's:
@import "tailwindcss";
/* Declare your custom layers relative to Tailwind's */
@layer components {
/* Your component styles — same layer as Tailwind components */
.card { /* ... */ }
}
@layer utilities {
/* Your utilities — same layer as Tailwind utilities */
.text-brand { color: var(--color-brand); }
}Debugging Layer Order
// Check which layers are active in the browser
const layers = [...document.styleSheets]
.flatMap(sheet => {
try { return [...sheet.cssRules]; } catch { return []; }
})
.filter(rule => rule.constructor.name === 'CSSLayerStatementRule')
.map(rule => rule.nameList);
console.log('Active layers:', layers);Conclusion
CSS cascade layers solve the specificity problem architecturally rather than symptomatically. Instead of fighting specificity with more specificity or resorting to !important, you declare an explicit ordering of style groups and let the browser enforce it predictably. For design systems, the @layer architecture eliminates the class of bugs that arise from specificity conflicts between base styles, components, utilities, and theme overrides. With Tailwind v4 using @layer internally and full browser support established, it is the right time to adopt this pattern in your own stylesheets.