back to blog
developmentApril 26, 2026 · 3 min read

CSS Cascade Layers: Finally Taking Control of Specificity

The @layer rule gives you explicit control over which CSS wins when rules conflict. Here's how cascade layers work and how they change the way you structure stylesheets.

Dan Holloran
Dan Holloran
Senior Frontend & Fullstack Developer
CSS Cascade Layers: Finally Taking Control of Specificity image

Specificity has always been one of CSS's most frustrating aspects. You write a reasonable selector, and it gets overridden by something with a higher specificity buried three files away. The usual fixes — adding more specific selectors, using !important, or reorganizing your stylesheet — all feel like symptoms of a deeper problem. CSS Cascade Layers (@layer) address that problem directly by letting you define an explicit ordering for groups of styles.

How Layers Work

A layer is a named bucket for CSS rules. Layers are ordered at declaration, and that order determines priority — later layers win over earlier ones, regardless of selector specificity:

css
/* Declare the layer order first */
@layer reset, base, components, utilities;

/* Rules in each layer */
@layer reset {
  *,
  *::before,
  *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }
}

@layer base {
  body {
    font-family: system-ui, sans-serif;
    line-height: 1.5;
    color: #1a1a1a;
  }

  a {
    color: inherit;
    text-decoration: underline;
  }
}

@layer components {
  .btn {
    display: inline-flex;
    align-items: center;
    padding: 0.5rem 1rem;
    border-radius: 0.375rem;
    font-weight: 500;
    cursor: pointer;
  }

  .btn-primary {
    background-color: #3b82f6;
    color: white;
  }
}

@layer utilities {
  .text-center {
    text-align: center;
  }
  .mt-4 {
    margin-top: 1rem;
  }
  .hidden {
    display: none !important;
  }
}

A .btn in the components layer will be overridden by any rule in the utilities layer, even if the utility has lower specificity. The layer order is the authority, not the selector.

Why This Matters for Third-Party CSS

The real power of @layer becomes clear when integrating third-party styles. Previously, overriding a UI library often required specificity gymnastics because the library's selectors were out of your control. Now you can wrap the library in a layer and your own styles automatically win:

css
/* Import a UI library into a low-priority layer */
@layer third-party {
  @import url("some-ui-library.css");
}

/* Your layer is declared later, so it wins */
@layer components {
  /* This overrides the UI library's .button styles
     without any specificity tricks */
  .button {
    border-radius: 0.25rem;
    font-family: var(--font-sans);
  }
}

No more !important, no more compounding specificity. You just put the library in a lower layer.

Unlayered Styles Always Win

An important gotcha: styles written outside any layer are treated as the highest priority layer automatically. This is a footgun if you're mixing layered and unlayered code:

css
@layer components {
  .card {
    background: white;
  }
}

/* Not in any layer — beats everything in @layer */
.card {
  background: gray;
} /* This wins */

The practical rule: once you start using @layer, put everything in a layer to avoid surprises.

Integrating with Tailwind CSS

Tailwind v4 uses cascade layers internally for its own base, components, and utilities. If you're writing custom CSS alongside Tailwind v4, you can slot into its layer structure:

css
@import "tailwindcss";

@layer components {
  .prose-card {
    @apply rounded-xl bg-white p-6 shadow-sm ring-1 ring-gray-200;
  }
}

Cascade layers won't rewrite how you write CSS day to day, but they change the power dynamics in your stylesheet. Specificity conflicts that used to require investigation and creative selector work now have a structural solution.