CSS @layer (Cascade Layers): The Complete Guide for 2026

February 12, 2026

CSS specificity has been the source of more frustration than any other part of the language. You write a clean selector, a third-party stylesheet overrides it with a more specific one, and suddenly you are adding !important everywhere just to make your own styles work. Cascade Layers, introduced through the @layer at-rule, solve this problem at the architectural level. They let you explicitly declare which groups of styles should take priority over others, completely independent of selector specificity.

This guide covers everything you need to use @layer in production: the syntax, how layers interact with specificity, nested layers, integration with CSS frameworks like Tailwind and Bootstrap, a practical migration strategy, and the pitfalls to avoid.

⚙ Related tools: Optimize your layered CSS with our CSS Minifier, and reference selector syntax in the CSS Selectors Cheat Sheet.

What Are Cascade Layers and Why They Matter

Before cascade layers, the CSS cascade resolved conflicts using three factors: importance (!important), specificity (how specific the selector is), and source order (which rule appears last). If a third-party library used a selector like .container .row .col-md-6 .card .card-body p, your simple .content p selector would lose — even though your styles should logically take priority.

Cascade layers insert a new step in the cascade, evaluated before specificity. The rule in the higher-priority layer wins, regardless of how specific either selector is. Layers beat specificity.

/* Layer order: later layers have higher priority */
@layer reset, base, components, utilities;

@layer utilities {
    .text-red { color: red; }  /* Simple class selector */
}

@layer base {
    body main article section div.content p.intro span.highlight {
        color: blue;  /* Highly specific selector */
    }
}

/* Result: .text-red wins because "utilities" is a higher layer */

The @layer Syntax

Declaring Layer Order

Declare the order of your layers upfront. This single line at the top of your stylesheet establishes the entire priority hierarchy:

/* First = lowest priority, last = highest */
@layer reset, base, components, utilities, overrides;

The order is locked at the point of first declaration — you cannot reorder layers later.

Defining Layers with Styles

@layer base {
    body {
        font-family: system-ui, -apple-system, sans-serif;
        line-height: 1.6;
        color: #e4e4e7;
    }
    a { color: #3b82f6; text-decoration: none; }
}

@layer components {
    .card {
        background: #1a1a2e;
        border: 1px solid rgba(255, 255, 255, 0.08);
        border-radius: 8px;
        padding: 1.5rem;
    }
    .btn {
        display: inline-flex;
        padding: 0.5rem 1rem;
        border-radius: 6px;
        font-weight: 500;
        cursor: pointer;
    }
}

You can add styles to the same layer from multiple blocks. They accumulate — the second block does not replace the first:

@layer components { .card { background: #1a1a2e; } }

/* Later in the same file or another file */
@layer components { .modal { position: fixed; inset: 0; } }

/* Both .card and .modal are in the "components" layer */

Importing into Layers

/* Import third-party CSS into a low-priority layer */
@import url("normalize.css") layer(reset);
@import url("bootstrap.css") layer(framework);

/* Import into an anonymous (unnamed) layer */
@import url("legacy-styles.css") layer;

/* Conditional imports */
@import url("desktop-grid.css") layer(layout) (min-width: 768px);
@import url("container-layouts.css") layer(layout)
    supports(container-type: inline-size);

How Layers Interact with Specificity

Layer priority is checked before specificity. The full cascade order in 2026 is: origin/importance, inline styles, cascade layers, specificity, source order.

@layer base, utilities;

@layer base {
    /* Specificity: 0-2-1 */
    main .content p.intro { color: darkblue; font-size: 1.2rem; }
}

@layer utilities {
    /* Specificity: 0-1-0 — lower, but wins because of layer order */
    .text-lg { font-size: 1.5rem; }
}

/* <p class="intro text-lg"> gets font-size: 1.5rem from utilities */

Unlayered Styles Beat All Layers

Styles not inside any @layer block have the highest priority:

@layer components {
    .btn { background: #3b82f6; color: white; padding: 0.5rem 1rem; }
}

/* Unlayered: beats the components layer */
.btn { background: #10b981; }

/* Result: green background from unlayered, color and padding from layer */

!important Reversal in Layers

With !important, priority is inverted — lower layers win. This lets foundational layers set truly immutable values. In practice, avoid !important in layers entirely.

@layer base, components;

@layer base      { .btn { color: black !important; } }
@layer components { .btn { color: white !important; } }

/* Normal: components wins (white). !important: base wins (black).
   The reversal is intentional but confusing — just don't use !important. */

Named vs Anonymous Layers

/* Named: can add more rules later from anywhere */
@layer components { .card { padding: 1rem; } }
@layer components { .badge { border-radius: 9999px; } }

/* Anonymous: created once, cannot be referenced again */
@layer { .legacy-widget { display: block; } }
@layer { .old-sidebar { width: 250px; } }
/* These are TWO separate anonymous layers, not one */

Anonymous layers are useful for one-off imports or legacy code that you want to contain without polluting your layer namespace.

Nested Layers

Layers can be nested using dot notation or nested @layer blocks:

/* Dot notation */
@layer framework.reset, framework.base, framework.components;

/* Block notation */
@layer framework {
    @layer reset {
        *, *::before, *::after { margin: 0; box-sizing: border-box; }
    }
    @layer components {
        .fw-btn { padding: 0.5rem 1rem; border-radius: 4px; }
    }
}

/* Equivalent dot notation for adding styles */
@layer framework.components {
    .fw-modal { position: fixed; inset: 0; }
}

All sublayers of a parent inherit the parent's priority rank. The entire framework layer (including all sublayers) is lower priority than any sibling layer declared after it:

@layer framework, custom;

@layer framework {
    @layer components { .btn { background: blue; } }
}
@layer custom {
    .btn { background: green; }  /* Wins: "custom" beats all of "framework" */
}

Using @layer with @import

/* Declare order first, then import into layers */
@layer reset, framework, custom, utilities;

@import url("modern-normalize.css") layer(reset);
@import url("bootstrap.min.css") layer(framework);

@layer custom {
    .navbar { background: #0f0f23; }
    .card { border-color: rgba(59, 130, 246, 0.3); }
}

@layer utilities {
    .mt-1 { margin-top: 0.25rem; }
    .hidden { display: none; }
}

Practical Architecture: The Five-Layer Pattern

This pattern works for projects of any size. Each layer has a clear purpose, and you always know where a new style belongs:

@layer reset, base, components, utilities, overrides;

@layer reset {
    *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
    img, picture, video, svg { display: block; max-width: 100%; }
    input, button, textarea, select { font: inherit; }
}

@layer base {
    :root {
        --color-bg: #0f0f23;    --color-text: #e4e4e7;
        --color-primary: #3b82f6; --color-border: rgba(255,255,255,0.08);
        --radius: 8px;           --space-md: 1rem;
    }
    body {
        font-family: system-ui, sans-serif;
        line-height: 1.6;
        color: var(--color-text);
        background: var(--color-bg);
    }
    a { color: var(--color-primary); text-decoration: none; }
}

@layer components {
    .card {
        background: #1a1a2e;
        border: 1px solid var(--color-border);
        border-radius: var(--radius);
        padding: 1.5rem;
    }
    .btn {
        display: inline-flex; align-items: center; gap: 0.5rem;
        padding: 0.5rem 1rem; border: none; border-radius: 6px;
        font-weight: 500; cursor: pointer;
        background: var(--color-primary); color: white;
    }
}

@layer utilities {
    .flex { display: flex; }
    .grid { display: grid; }
    .hidden { display: none; }
    .text-center { text-align: center; }
    .sr-only { position: absolute; width: 1px; height: 1px;
        overflow: hidden; clip: rect(0,0,0,0); }
}

@layer overrides {
    .hero-section .btn { padding: 1rem 2.5rem; font-size: 1.1rem; }
    [data-loading="true"] { opacity: 0.6; pointer-events: none; }
}

Managing Third-Party CSS with Layers

Before layers, framework styles with high specificity required !important to override. With layers, you wrap the framework in a low-priority layer and your styles automatically win:

@layer reset, vendor, app;

@import url("sanitize.css") layer(reset);
@import url("some-widget-library.css") layer(vendor);

@layer app {
    /* This simple selector beats ANY selector in vendor or reset */
    .widget-title { font-size: 1.25rem; color: var(--color-text); }
    .widget-container { border-radius: var(--radius); }
}

Multiple Libraries with Individual Layers

@layer reset, icons, datepicker, rich-text, app;

@import url("normalize.css") layer(reset);
@import url("material-icons.css") layer(icons);
@import url("flatpickr.css") layer(datepicker);
@import url("prosemirror.css") layer(rich-text);

@layer app {
    .icon { font-size: 1.25rem; vertical-align: middle; }
    .flatpickr-calendar { background: #1a1a2e; }
}

@layer with CSS Frameworks

Tailwind CSS

Tailwind v3.1+ has native cascade layer support. Wrap the Tailwind output in a parent layer to keep custom styles at higher priority:

@layer framework, custom;

@layer framework {
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
}

@layer custom {
    .prose h2 {
        color: var(--color-primary);
        border-bottom: 1px solid var(--color-border);
    }
    .sidebar .nav-link { padding: 0.75rem 1rem; border-radius: 6px; }
}

Bootstrap

@layer bootstrap, app;

@import url("bootstrap.min.css") layer(bootstrap);

@layer app {
    /* Override Bootstrap without matching its specificity */
    .btn { border-radius: 8px; font-weight: 600; }
    .navbar { background: #0f0f23; }
    .form-control { background: #1a1a2e; color: #e4e4e7; }
}

Generic Wrapper Pattern

/* Works with any CSS framework */
@layer framework, project;
@import url("framework.css") layer(framework);

@layer project {
    /* Simple selectors are all you need */
}

Browser Support and Fallback Strategies

CSS @layer shipped in all major browsers in early 2022:

As of 2026, global browser support exceeds 96%. The graceful degradation is excellent: browsers that do not support @layer ignore the layer declarations and process the CSS rules inside them normally using specificity and source order.

/* Feature detection */
@supports at-rule(@layer) {
    @layer base { .card { background: #1a1a2e; } }
}

/* Browsers without @layer support ignore the wrapper
   and apply the rules via normal specificity + source order */

Real-World Migration Guide

Migrating an existing codebase to cascade layers works best incrementally. Here is the recommended sequence:

Step 1: Declare your layer order at the very first line of your main CSS entry point:

@layer reset, vendor, base, components, utilities, overrides;

Step 2: Wrap third-party CSS first — this is the lowest-risk, highest-reward change:

/* Before */
@import url("normalize.css");
@import url("library.css");

/* After */
@import url("normalize.css") layer(reset);
@import url("library.css") layer(vendor);

Step 3: Move your base and component styles into their respective layers:

@layer base {
    body { font-family: system-ui, sans-serif; color: #e4e4e7; }
}
@layer components {
    .card { /* ... */ }
    .btn { /* ... */ }
}

Step 4: Keep overrides unlayered initially. During migration, unlayered styles have the highest priority automatically. Once stable, move them into an overrides layer and remove !important declarations.

Step 5: Audit and remove !important. With proper layer ordering, most or all !important declarations become unnecessary. Each one you remove makes your CSS more predictable.

Common Pitfalls and Debugging

Pitfall 1: Unlayered Styles Always Win

@layer utilities { .text-red { color: red; } }

p { color: black; }  /* Unlayered: beats utilities layer! */

/* Fix: put the p rule in a lower layer */
@layer base { p { color: black; } }
@layer utilities { .text-red { color: red; } }  /* Now wins */

Pitfall 2: Layer Order Is Set on First Declaration

/* First appearance sets the order */
@layer components { .card { background: blue; } }
@layer base { body { color: white; } }

/* Order is: components, base — "base" is HIGHER priority!
   Fix: always declare order explicitly at the top */
@layer base, components;

Pitfall 3: Cannot Reorder After Declaration

@layer A, B, C;  /* Order locked: A < B < C */
@layer C, A, B;  /* Ignored — order is still A < B < C */

Pitfall 4: Nested Layer Priority Scope

@layer framework, app;

@layer framework {
    @layer utilities { .fw-bold { font-weight: 700; } }
}
@layer app {
    @layer reset { .fw-bold { font-weight: 400; } }
}

/* "app.reset" beats "framework.utilities" because the entire
   "framework" layer is lower than "app" */

Pitfall 5: @layer Inside @media

/* The layer is created unconditionally — only the rules are conditional */
@media (min-width: 768px) {
    @layer responsive { .sidebar { display: block; } }
}
/* The "responsive" layer exists at all widths. The .sidebar rule
   only applies at 768px+. */

Debugging Layers in DevTools

⚙ Optimize: After organizing your CSS into layers, run it through our CSS Minifier to strip whitespace and comments for production. Layer declarations are preserved during minification.

Complete Example: Layered Design System

/* ===== main.css ===== */
@layer reset, vendor, tokens, base, layouts, components, utilities, overrides;

@import url("modern-normalize.css") layer(reset);
@import url("prism-theme.css") layer(vendor);

@layer tokens {
    :root {
        --blue-500: #3b82f6;  --gray-800: #1a1a2e;
        --gray-900: #0f0f23;  --white-soft: #e4e4e7;
        --border-subtle: rgba(255, 255, 255, 0.08);
        --font-sans: system-ui, -apple-system, sans-serif;
        --font-mono: 'Fira Code', monospace;
        --radius-sm: 4px;  --radius-md: 8px;  --radius-lg: 12px;
    }
}

@layer base {
    body { font-family: var(--font-sans); color: var(--white-soft);
           background: var(--gray-900); line-height: 1.6; }
    code, pre { font-family: var(--font-mono); }
}

@layer layouts {
    .page-grid {
        display: grid;
        grid-template-columns: 1fr min(75ch, 100% - 2rem) 1fr;
    }
    .page-grid > * { grid-column: 2; }
    .page-grid > .full-bleed { grid-column: 1 / -1; }
}

@layer components {
    .btn-primary {
        background: var(--blue-500); color: white;
        padding: 0.6rem 1.2rem; border: none;
        border-radius: var(--radius-sm); font-weight: 500;
    }
    .code-block {
        background: var(--gray-800);
        border: 1px solid var(--border-subtle);
        border-radius: var(--radius-md);
        padding: 1.25rem; overflow-x: auto;
    }
}

@layer utilities {
    .visually-hidden { position: absolute; width: 1px; height: 1px;
        overflow: hidden; clip: rect(0,0,0,0); }
    .text-center { text-align: center; }
}

@layer overrides {
    .landing-page .btn-primary {
        padding: 0.8rem 2rem; font-size: 1.1rem;
        border-radius: var(--radius-lg);
    }
}

Every developer on the team knows where new styles go. The layer declaration at the top serves as documentation of the CSS architecture, and specificity wars become impossible.

Summary

  1. Layers beat specificity. A simple selector in a higher layer always wins over a complex selector in a lower layer.
  2. Declare order upfront. Put @layer reset, base, components, utilities; at the top of your CSS. The first declaration locks the order.
  3. Unlayered styles are highest priority. Styles outside any @layer block beat all named layers. Use this for incremental migration.
  4. Wrap third-party CSS in layers. Use @import url("lib.css") layer(vendor) to contain framework styles at low priority.
  5. Nest layers for organization. Use @layer framework.components to create sublayers that inherit their parent's priority rank.
  6. Avoid !important in layers. The priority reversal is confusing. Layers should eliminate the need for !important entirely.
  7. Migrate incrementally. Start with third-party CSS, then move your own styles layer by layer.
  8. Browser support is universal. Over 96% global support. Unsupported browsers process the rules normally using specificity.

Cascade layers are the most significant addition to CSS architecture since CSS Grid and Flexbox changed layout. For more CSS techniques, see the CSS Performance Optimization guide and the CSS Selectors Cheat Sheet.

Frequently Asked Questions

What is CSS @layer and why should I use it?

CSS @layer lets you explicitly control which groups of styles take priority, independent of selector specificity. Instead of fighting with !important, you declare layers in a priority order. Styles in later layers always beat earlier layers, regardless of selector complexity.

How do cascade layers interact with specificity?

Layers are evaluated before specificity in the cascade. The declaration in the higher-priority layer wins unconditionally. Specificity only breaks ties within the same layer. A simple p selector in a higher layer beats #main .content article p.intro in a lower layer.

What happens to styles not inside any @layer?

Unlayered styles have the highest priority. They always beat styles inside any named layer. This enables incremental adoption — wrap lower-priority styles in layers while keeping critical overrides unlayered.

Can I use @layer with Tailwind CSS or Bootstrap?

Yes. Tailwind v3.1+ has built-in layer support. Wrap the output in @layer framework { ... } to keep custom styles at higher priority. For Bootstrap, use @import url("bootstrap.css") layer(framework) to place it in a low-priority layer.

What is the recommended layer order?

A proven architecture: @layer reset, base, components, utilities, overrides;. Reset contains normalization. Base has global tokens and typography. Components holds UI elements. Utilities are single-purpose helpers. Overrides handles page-specific adjustments.

Are cascade layers supported in all browsers?

@layer is supported in Chrome 99+, Firefox 97+, Safari 15.4+, and Edge 99+. As of 2026, global support exceeds 96%. Browsers that do not understand @layer ignore the wrapper and process rules normally.

Related Resources

CSS Grid Complete Guide
Master two-dimensional layout with CSS Grid
CSS Flexbox Complete Guide
One-dimensional layout patterns with Flexbox
CSS Minifier
Minify and optimize your layered CSS for production
CSS Performance Optimization
Optimize CSS loading, rendering, and runtime performance
CSS Selectors Cheat Sheet
Quick reference for all CSS selector types and specificity
CSS Custom Properties Guide
Design tokens, theming, and runtime CSS variables