CSS Cascade Layers (@layer) deep dive: specificity reset, layer order, and the `!important` reversal

4 min read

CSS @layer is a restructuring of the cascade, not a styling helper. It dissolves the specificity arms race that produces “library styles I can’t override,” “stack five !importants to win.” This article walks through how layers order, nest, and interact with !important — including a counterintuitive rule that reverses layer priority for important declarations.

Why layers exist

Traditional cascade priority:

  1. Origin (user > author > browser default)
  2. !important (above normal)
  3. Specificity (id, class, type weights)
  4. Source order (last wins)

Specificity comparisons happen within the same origin and importance, which is what produces “to override the library’s .btn-primary, I have to write body .container .btn-primary” — the specificity arms race.

@layer resolves this by adding a new dimension above specificity.

Basic syntax

Named layers

@layer reset, base, components, utilities;

@layer reset {
	* {
		margin: 0;
	}
}

@layer base {
	body {
		font-family: system-ui;
	}
}

@layer components {
	.btn {
		padding: 0.5rem 1rem;
	}
}

@layer utilities {
	.mt-4 {
		margin-top: 1rem;
	}
}

The @layer reset, base, components, utilities; declaration fixes the order: later layers win. utilities > components > base > reset.

Later layers win, regardless of specificity

@layer base {
	.text {
		color: black;
	}
}

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

<p class="text"> is red. The utilities layer is later than base, so it wins. Specificity inside the rules doesn’t enter into it.

@layer base {
	#main .text {
		color: black;
	} /* specificity 0,1,1,1 */
}

@layer utilities {
	.text {
		color: red;
	} /* specificity 0,0,1,0 */
}

Still red. Specificity comparison happens only within a layer.

Unlayered styles

Styles not wrapped in @layer are stronger than every layer:

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

.text {
	color: green; /* unlayered */
}

Result: green. The counterintuitive rule: unlayered beats layered.

Priority becomes:

[strongest] unlayered > later layers > earlier layers [weakest]

This makes a clean architecture possible: put library / framework styles in layers, keep your own app styles unlayered so they win automatically:

/* Frameworks layered */
@import 'tailwind.css' layer(framework);
@import 'reset.css' layer(reset);

/* Your app — unlayered, so it always wins */
.my-button {
	background: tomato;
}

The @import url(...) layer(name) syntax assigns external CSS to a named layer.

!important reverses layer order

The most surprising behavior. With !important, layer order flips.

@layer base {
	.text {
		color: black !important;
	}
}

@layer utilities {
	.text {
		color: red !important;
	}
}

Normally utilities wins. With !important, base wins: black.

The intuition

Normal rules say “later layers win” because the convention is more specific layers go later (reset → base → components → utilities). For !important, the meaning is “this value must absolutely apply.” A library author expressing !important shouldn’t be silently overridden by a later layer — so !important flips the priority so earlier layers win.

Unlayered + !important

[strongest] earlier layer's !important > later layer's !important > unlayered !important
            > unlayered normal > later layer's normal > earlier layer's normal [weakest]

Unlayered with !important is not the strongest in the important world — unlayered < layered for important declarations.

Layer nesting

@layer components {
	@layer cards {
		.card {
			padding: 1rem;
		}
	}
	@layer buttons {
		.btn {
			padding: 0.5rem;
		}
	}
}

Creates components.cards and components.buttons. Order: components.cards < components.buttons (later wins).

Dot syntax for adding to nested layers from elsewhere:

@layer components.cards {
	.card-header {
		font-weight: bold;
	}
}

Anonymous layers

@layer {
	.foo {
		color: red;
	}
}

Unnamed. Cannot be referenced from elsewhere, so you can’t add to it later. Prefer named layers in production.

Browser support and operational notes

  • Chrome 99+ (2022), Firefox 97+, Safari 15.4+. All modern browsers.
  • IE 11: not supported (verify your project’s browser baseline).
  • Declare layer order early: put the @layer name1, name2, ...; statement at the very top of your CSS, before any imports. The first @layer name mention establishes the layer’s position:
<style>
	@layer reset, base, components, utilities;
</style>
<link rel="stylesheet" href="reset.css" />
<link rel="stylesheet" href="tailwind.css" />

Without that upfront declaration, layer order depends on bundler concatenation order — surprising and bug-prone.

Recommended architecture

/* Reset → base → design system → app → utilities */
@layer reset, base, design-system, app, utilities;

@layer reset {
	/* normalize.css equivalent */
}

@layer base {
	body {
		font-family: var(--font-base);
	}
}

@layer design-system {
	/* shadcn / Skeleton UI / your component library */
}

@layer app {
	/* app-specific overrides */
}

@layer utilities {
	/* tailwind utilities, etc. */
}

Five layers, rarely needs reshuffling. Add a new external library with @import url(lib.css) layer(design-system).

Anti-patterns

!important inside @layer

@layer utilities {
	.mt-4 {
		margin-top: 1rem !important;
	}
}

If you use @layer to control priority, !important is unnecessary — and using it activates the layer-reversal behavior, making the cascade unreadable. Treat !important as forbidden once @layer is adopted.

Mixing layered and unlayered

Once you start using @layer, either put all your CSS in layers, or keep all of it unlayered. Mixing produces “unlayered styles unexpectedly winning” bugs that are hard to track down.

Summary

@layer solves the specificity-arms-race problem architecturally: you define layer order once, and library vs app styles compose without specificity gymnastics. The two surprises to remember are !important reverses layer order and unlayered styles beat layered ones. With those internalized, a clean three-tier “design system + utilities + app overrides” composition stays readable.

For CSS formatting and structure inspection, the CSS formatter tool covers the standard cases.