Home / Snippets / Layout /

Utility layer

Declare utilities last in your @layer order so utility classes always win — no !important, no specificity battles.

Layer order (lowest → highest priority)
reset base components utilities wins
.btn + .text-center — utility overrides component
.hidden — element suppressed by utility layer

The card below is hidden via .u-hidden (display: none).

You should not see this
Element present in DOM, hidden by utility layer.
.mt-1 — spacing utility stacked on component
Widely Supported
layoutno-js

Quick implementation

/* 1. Declare layer order — utilities must come last */
@layer reset, base, components, utilities;

/* 2. Component styles sit in the components layer */
@layer components {
  .btn {
    display: inline-block;
    padding: 0.6rem 1.25rem;
    background: oklch(0.52 0.22 265);
    color: oklch(0.97 0.01 260);
    border-radius: 0.375rem;
    font-weight: 600;
    text-align: left; /* default alignment */
  }
}

/* 3. Utilities always override — no !important needed */
@layer utilities {
  .text-center { text-align: center; }
  .mt-1        { margin-top: 1rem; }
  .hidden      { display: none; }
}

/*
  Result: .btn.text-center gets text-align: center
  from the utilities layer, even though .btn sets
  text-align: left — layer order wins, not specificity.
*/

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer setting up a scalable CSS architecture
using cascade layers (@layer).

Goal: Implement the utility-layer pattern — place all utility classes in a
layer declared after components so they always override component styles
without needing !important or higher specificity selectors.

Technical constraints:
- Open every stylesheet with a single @layer order declaration:
    @layer reset, base, components, utilities;
  This must appear before any layer block definitions.
- Group reset/normalise rules inside @layer reset { … }.
- Group design-token and element defaults inside @layer base { … }.
- Group reusable UI components (.btn, .card, .badge, etc.)
  inside @layer components { … }.
- Group atomic utility classes (.text-center, .mt-1, .hidden, etc.)
  inside @layer utilities { … }.
- Use oklch() for all colour values — no hex or rgba.
- Use CSS custom properties (var(--bg), var(--text), var(--accent), etc.)
  for design tokens throughout.
- Do not use !important anywhere — rely on layer order instead.

Framework variant (pick one):
A) Pure CSS — a single stylesheet file organised into the four layers above,
   with at least six utility classes and two component classes demonstrating
   the override behaviour.
B) PostCSS — use postcss-cascade-layers to polyfill @layer for older browsers;
   show the PostCSS config and explain which browsers gain support.

Edge cases to handle:
- Styles written outside any @layer block have higher priority than any
  named layer — warn about inline <style> blocks and third-party stylesheets
  that may sit outside the layer system and unexpectedly win.
- When importing third-party CSS (e.g. a reset), wrap it in a layer import:
    @import url("reset.css") layer(reset);
  so it participates in the layer order rather than overriding everything.
- Specificity still applies within a single layer — two utilities with the
  same specificity fall back to source order, so keep utilities as low
  specificity (single class) as possible.
- Document the @layer order declaration prominently; a developer adding a
  new layer block without updating the order declaration will create a new
  anonymous layer that wins over everything.

Return CSS only (or PostCSS config + CSS if variant B is chosen).

Why this eliminates !important

The cascade resolves style conflicts in a specific sequence: origin and importance first, then cascade layers, then specificity, then source order. @layer slots into that sequence before specificity is even checked. When utilities is declared after components in the layer order, every rule inside @layer utilities wins over every rule inside @layer components — regardless of selector specificity. A single-class utility like .text-center beats a three-class component selector like .card .btn.active without needing !important.

This is exactly how utility-first frameworks such as Tailwind CSS v4 work internally. Tailwind wraps its generated utilities in a @layer utilities block and its component/base styles in lower-priority layers, so an overriding utility class always wins without any specificity gymnastics.

The layer order declaration

The single most important rule of the pattern: declare your layer order once, at the top of your first stylesheet, before any @layer block definitions.

@layer reset, base, components, utilities;

This statement does two things. First, it creates (or confirms) each named layer. Second, it fixes their priority order for the entire document — layers declared earlier in this list have lower priority. All subsequent @layer block statements add rules to whichever named layer they reference; the order in which the blocks appear in source no longer affects priority. That means you can split your layers across many files and import them in any order — the declared sequence still governs who wins.

Styles written outside any layer block sit above all named layers in priority. This is a common footgun: a third-party stylesheet included without a layer wrapper will override everything in your layer system. Wrap third-party imports with @import url("lib.css") layer(vendor); and include vendor early in your order declaration to contain it.

Works with Tailwind and utility-first frameworks

Tailwind CSS v4 adopts @layer natively. Its build output wraps base styles, component styles, and utility classes in the appropriate layers. If you write custom component CSS alongside Tailwind, you can slot it into the layer system by wrapping it in @layer components { … } — Tailwind's utility classes will still override it because its utilities land in a higher layer.

For pre-v4 Tailwind or other utility frameworks that do not yet use @layer, you can still apply the pattern to your own code. Declare the layer order, put your application components inside @layer components, and put your custom overrides inside @layer utilities. The framework's stylesheet will sit outside any layer and therefore win over all named layers — but your utilities will at least beat your own components, which reduces the need for !important in day-to-day development.

The postcss-cascade-layers plugin polyfills @layer for browsers that do not support it by converting layer priority into specificity adjustments at build time. This gives you the authoring ergonomics of layers while shipping code that works in older browsers.