Complex component systems built on cascade layers. The intermediate tutorial introduced @layer; here we scale that to real projects with third-party CSS, design systems, page-level overrides, and multiple authors who will not read your README.
The problem at scale
One stylesheet with four layers works fine. A product with a reset, a vendor UI kit, a shared design system, app-specific components, marketing pages, and A/B test overrides does not. Without an explicit layer contract, specificity wars return – just slower and more expensive.
Layers are not magic. They are a shared agreement about precedence. Document the stack once, enforce it in code review, and treat unlayered CSS as a smell unless it lives in a named catch-all layer at the end.
A production layer stack
A stack that survives hand-offs between teams might look like this:
@layer
vendor-reset,
vendor-components,
tokens,
base,
layout,
components,
utilities,
overrides;
@import url("normalize.css") layer(vendor-reset);
@import url("vendor-ui.css") layer(vendor-components);
@import url("tokens.css") layer(tokens);
@import url("base.css") layer(base);
@import url("layout.css") layer(layout);
@import url("components.css") layer(components);
@import url("utilities.css") layer(utilities);
Vendor CSS sits low. Your tokens and base styles sit above it. Components beat vendor widgets. Utilities beat components for deliberate one-offs. The overrides layer is for documented exceptions – not for ‘I needed to win today’ hacks.
Sub-layers inside components
Large component libraries can declare nested layers within a file or import:
@layer components {
@layer primitives, patterns, compositions;
@layer primitives {
.btn { … }
.input { … }
}
@layer patterns {
.form-row { … }
.media-object { … }
}
@layer compositions {
.checkout-panel { … }
}
}
Later sub-layers beat earlier ones inside the parent layer. Compositions can override primitives without selector arms races. Name sub-layers for what they contain, not for priority feelings.
Layer-aware component APIs
Publish components with low-specificity selectors inside the components layer. Consumers override via utilities, tokens, or the overrides layer – not by copying your selectors with extra classes prepended.
@layer components {
:where(.ds-card) {
display: flex;
flex-direction: column;
gap: var(--space-3);
padding: var(--space-4);
background: var(--surface-raised);
border-radius: var(--radius-md);
}
:where(.ds-card__title) {
font-size: var(--text-lg);
font-weight: 600;
}
}
:where() keeps specificity at zero inside the layer. A single utility class or a token change on a parent can restyle the card without fighting .ds-card.ds-card--featured h3.
Integrating third-party CSS
When a library does not support layers, wrap its import in a low layer and never edit the vendor file directly:
@import url("bootstrap.min.css") layer(vendor-components);
@layer components {
:where(.btn) {
/* your button replaces vendor look without !important */
border-radius: var(--radius-pill);
font-weight: 600;
}
}
If the vendor uses heavy !important, your options narrow: fork, shadow DOM, or accept a parallel stylesheet loaded with lower priority. Layers fix ordering between your code; they do not rewrite someone else’s important declarations.
Feature flags and experiments
A/B tests and feature flags often inject CSS. Give experiments their own layer above components but below overrides, so winning variants merge cleanly and losing ones delete one file:
@layer experiments;
@layer experiments {
[data-exp="checkout-v2"] .summary {
position: sticky;
top: 1rem;
}
}
Governance
- Put the layer order in one file everyone imports first.
- Lint for unlayered rules in CI where possible.
- Code review question: ‘Which layer does this belong in?’
- Document what may never go in
overrides(brand colours, typography scale).
The next tutorial covers advanced container query patterns – size and style queries combined, fallbacks, and component libraries that respond to their slot without viewport hacks.

