Skip to main content

CSS @layer as Architecture: Native ITCSS

@layer (Cascade Layers) is the most important modern CSS feature for methodology-based architecture. It lets you declare the order of the cascade explicitly — so specificity stops being a war and becomes a system you control.

1. The Problem @layer Solves

Without layers, the cascade is determined by source order + specificity. This leads to:

/* styles.css — loaded first */
.btn-primary { background: blue; padding: 1rem; }

/* theme.css — loaded second */
.btn-primary { background: red; } /* Wins — last declaration */

/* plugin.css — loaded third */
.btn-primary { background: green !important; } /* Wins everything */

/* your override — loaded fourth */
.btn-primary { background: var(--color-primary); } /* LOSES to !important */

The typical result: !important escalation, specificity fights, unpredictable overrides.

With @layer, you declare a priority order upfront. Higher layers always win — regardless of source order or specificity:

/* Declare the order. Later = higher priority. */
@layer reset, theme, plugin, components, utilities;

/* Now even if plugin.css uses !important, your components layer beats it */
@layer components {
.btn-primary { background: var(--color-primary); } /* WINS */
}

2. How @layer Works

Basic Syntax

/* Option 1: Declare all layers upfront (recommended) */
@layer reset, base, components, utilities;

/* Option 2: Declare inline with content */
@layer reset {
*, *::before, *::after { box-sizing: border-box; margin: 0; }
}

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

/* Option 3: Append to a layer from anywhere */
@layer components {
.card { background: var(--color-surface); }
}
/* Later in the file — same layer */
@layer components {
.btn { padding: 0.7rem 1.5rem; }
}

Layer Priority Rules

Layer Order: @layer A, B, C, D;

Priority: D > C > B > A ← Later = higher priority

Unlayered CSS beats ALL layers:
.btn { color: red; } ← Beats everything, including D
@layer utilities { .btn { color: blue; } } ← Loses to unlayered

[!IMPORTANT] Unlayered CSS always beats @layer. This is crucial — if a WordPress plugin or third-party stylesheet doesn't use layers, it wins over everything. Wrap third-party CSS in a low-priority layer to prevent this.


3. @layer as Native ITCSS

ITCSS defined 7 conceptual layers in 2015. @layer (2022) makes those layers native CSS:

/* ITCSS Layer → @layer equivalent */
/* 1. Settings → tokens (no CSS output — defined as :root vars) */
/* 2. Tools → mixins (no CSS output — Sass preprocessor) */
/* 3. Generic → reset */
/* 4. Elements → elements */
/* 5. Objects → objects */
/* 6. Components → components */
/* 7. Utilities → utilities */

@layer reset, elements, objects, components, utilities;

Full ITCSS-Aligned @layer Setup

/* ── main.css ── */

/* Step 1: Declare all layers in order (lowest to highest priority) */
@layer reset, elements, objects, components, utilities;

/* Step 2: Import external stylesheets INTO a layer to control their priority */
@layer reset {
@import url('https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css');
}

/* Step 3: Fill each layer */

/* ── Layer: reset ── */
@layer reset {
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: none; }
img, video, svg { display: block; max-width: 100%; height: auto; }
input, button, textarea, select { font: inherit; }
}

/* ── Layer: elements (bare HTML, no classes) ── */
@layer elements {
body {
font-family: var(--font-body);
font-size: var(--text-base);
line-height: 1.6;
color: var(--color-text);
background: var(--color-bg);
}
h1, h2, h3, h4 { font-family: var(--font-heading); line-height: 1.2; }
a { color: var(--color-primary); transition: color 0.2s; }
p { line-height: 1.7; }
}

/* ── Layer: objects (structure only, skin-neutral) ── */
@layer objects {
.o-container {
width: 100%;
max-width: 1200px;
margin-inline: auto;
padding-inline: clamp(1rem, 4vw, 2rem);
}
.o-grid {
display: grid;
gap: var(--grid-gap, 1.5rem);
grid-template-columns: var(--grid-cols, repeat(auto-fill, minmax(280px, 1fr)));
}
.o-stack > * + * { margin-top: var(--stack-gap, 1.5rem); }
.o-media { display: flex; align-items: flex-start; gap: 1.25rem; }
.o-media__body { flex: 1; min-width: 0; }
}

/* ── Layer: components (styled UI components) ── */
@layer components {
.c-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.7rem 1.5rem;
border-radius: var(--radius-full);
font-weight: 600;
border: 2px solid transparent;
cursor: pointer;
transition: all 0.2s ease;
}
.c-btn--primary { background: var(--color-primary); color: white; }
.c-btn--primary:hover { background: var(--color-primary-dark); }

.c-card {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-xl);
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.c-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
}

/* ── Layer: utilities (highest priority, always win) ── */
@layer utilities {
.u-hidden { display: none !important; }
.u-sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }
.u-truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.u-mt-auto { margin-top: auto !important; }
.u-mx-auto { margin-inline: auto !important; }
.u-w-full { width: 100% !important; }
.u-text-center { text-align: center !important; }
}

4. Wrapping Third-Party CSS in a Layer

This is one of the most powerful use cases — taming WordPress plugin styles:

/* Control third-party priority by wrapping in a low-priority layer */
@layer reset, third-party, elements, objects, components, utilities;

/* Contact Form 7 — now lower priority than your components */
@layer third-party {
@import url('/wp-content/plugins/contact-form-7/includes/css/styles.css');
}

/* WooCommerce — now lower priority */
@layer third-party {
@import url('/wp-content/plugins/woocommerce/assets/css/woocommerce.css');
}

/* Your components ALWAYS win now — no !important needed */
@layer components {
.wpcf7-form-control { border: 1.5px solid var(--color-border) !important; } /* Wins cleanly */
}

[!TIP] This technique eliminates 90% of !important usage in WordPress child themes. Wrap plugin stylesheets in @layer third-party and your component layer automatically overrides them.


5. Nested Layers

Layers can be nested for more granular control:

/* Nested layers for a large design system */
@layer design-system {
@layer tokens, reset, base, objects, components;

@layer components {
@layer forms, navigation, cards, modals;

@layer cards {
.c-card { background: var(--color-surface); }
}

@layer modals {
.c-modal { position: fixed; inset: 0; z-index: var(--z-modal); }
}
}
}

6. @layer + Media Queries

Layers work inside media queries:

/* Mobile-first components layer */
@layer components {
.c-nav__list { display: none; }

@media (min-width: 768px) {
.c-nav__list { display: flex; }
}
}

/* Container queries inside a layer */
@layer components {
.card-wrapper { container-type: inline-size; }

@container (min-width: 400px) {
.c-card { flex-direction: row; }
}
}

7. @layer + CSS Nesting: The Ultimate Combination

@layer components {
.c-card {
background: var(--color-surface);
border-radius: var(--radius-xl);

/* Nesting inside the layer */
.c-card__title { font-size: var(--text-xl); }
.c-card__body { padding: var(--space-6); }

&.c-card--featured {
border: 1px solid var(--color-primary);
.c-card__title { color: var(--color-primary); }
}

&:hover { transform: translateY(-4px); }
}
}

8. Browser Support and Progressive Enhancement

Browser@layer SupportSince
Chromev99 (March 2022)
Firefoxv97 (Feb 2022)
Safariv15.4 (March 2022)
Edgev99 (March 2022)
Overall~96%

@layer is safe to use in production today.

/* If you need a fallback for ancient browsers (rare): */
@supports (not (@layer x {})) {
/* Legacy fallback — almost never needed */
.c-btn { background: var(--color-primary); }
}

9. @layer vs Specificity vs !important

Understanding the full cascade priority order:

Priority (highest → lowest):
──────────────────────────────────────────────────────
1. Transitions (in-progress transitions)
2. !important user agents (browser UA !important)
3. !important user (reader stylesheet !important — rare)
4. !important author (your !important)
↳ !important utilities layer (highest of your @layers + !important)
↳ !important components layer
↳ !important unlayered !important
5. Normal author
↳ Unlayered CSS ← Beats all author @layers
↳ @layer utilities ← Highest @layer
↳ @layer components
↳ @layer objects
↳ @layer elements
↳ @layer reset ← Lowest @layer
6. Normal user
7. User agent (browser defaults)
──────────────────────────────────────────────────────

10. Complete WordPress Layer Architecture

/* child-theme/style.css */

/* Declare layers: lowest priority → highest priority */
@layer wp-reset, wp-blocks, third-party, elements, objects, components, theme, utilities;

/* WordPress reset and base */
@layer wp-reset {
*, *::before, *::after { box-sizing: border-box; }
}

/* Gutenberg block styles — contained so they never leak into components */
@layer wp-blocks {
.wp-block-paragraph { max-width: 72ch; }
.wp-block-heading { font-family: var(--font-heading); }
.wp-block-image img { border-radius: var(--radius-md); }
}

/* Plugin CSS wrapped so YOUR components win */
@layer third-party {
/* CF7, WooCommerce, etc. reset here via @import */
}

/* Page elements */
@layer elements {
body { font-family: var(--font-body); color: var(--color-text); }
}

/* Layout patterns */
@layer objects {
.o-container { max-width: 1200px; margin-inline: auto; }
}

/* Styled components — always win over wp-blocks and third-party */
@layer components {
.c-btn-primary { background: var(--color-primary); color: white; }
.wpcf7-form-control { border: 1.5px solid var(--color-border); border-radius: var(--radius-md); }
.woocommerce #place_order { background: var(--color-primary); border-radius: var(--radius-md); }
}

/* WordPress customizer token overrides */
@layer theme {
:root {
--color-primary: #6c63ff;
--font-body: 'Inter', sans-serif;
}
}

/* Always-win utilities */
@layer utilities {
.u-hidden { display: none !important; }
.u-sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; }
}