Chapter 9 — CSS Architecture: Thinking in Layers

9.1 — The Architecture Problem

A stylesheet is a list of rules. A system is a set of guarantees.

quell-light works because it is small. One breakpoint. A lean component set. A token foundation with nowhere to hide. At that scale, discipline is easy to enforce by hand. A single author can hold the whole file in their head.

That doesn’t survive growth. Add breakpoints. Add components. Add contributors who didn’t write the original rules and don’t share the original instincts. The failure mode is always the same, and it’s always quiet at first: a selector gets a little more specific than it needs to be, because that was the fast way to fix a bug under deadline. A component gets an !important, because the cascade wasn’t behaving and there wasn’t time to find out why. Six months later, nobody remembers which override was load-bearing and which was an accident. The fix for any new bug becomes “add more specificity and hope.” That is a specificity war, and specificity wars are what happen when a codebase has rules but no contract.

The cost isn’t aesthetic. It’s structural. A system without an enforced cascade order can’t be reasoned about — every change requires checking the entire file for conflicts, because nothing guarantees where a given rule sits relative to any other. Predictability is the actual product a design system sells. Lose it, and the token system, the component library, the naming conventions — all of it — stop mattering, because nobody can trust what wins.

quell.css v2 exists to answer this before it happens, not after. The answer isn’t more discipline. Discipline doesn’t scale past one person. The answer is a cascade the browser itself enforces — a contract that holds regardless of who’s writing the next rule, or how many rules there are.


9.2 — @layer as Architecture

@layer is not a specificity workaround. It is a declared precedence system that sits above specificity entirely.

Before @layer, cascade order was a byproduct — source order, selector weight, the accident of which file loaded last. Two developers fighting over which rule wins were really fighting over selector complexity, because selector complexity was the only lever available. That’s the arms race: an ID selector beats a class, three classes beat two, !important beats everything until the next !important shows up. None of it is a decision. All of it is a workaround.

@layer replaces the workaround with a contract. A single statement —

@layer theme, tokens, base, layout, components, interactive, utilities, overrides;

— declares, once, at the top of the file, exactly which category of rule beats which other category, permanently, independent of selector weight. A one-element ID selector inside components still loses to a zero-specificity :where() selector inside utilities, because utilities was declared later in the layer list. Specificity still exists inside a layer. It stops mattering between layers. The layer order is the argument; the selector is just where the rule lives.

This is why @layer is architecture and not decoration. It doesn’t make the cascade quieter through clever selectors. It removes selector weight from the conversation entirely and replaces it with an explicit, readable, front-loaded statement of intent. Anyone opening the file for the first time reads eight words and knows the entire precedence order of the system, before reading a single rule.


9.3a — The Cascade Contract

quell-base establishes zero-specificity ground, quell-core sits above it unlayered, quell-light consumes tokens and never hardcodes. Volume One taught that contract in practice. Here is the mechanism underneath it, formalized.

quell-base.css declares five named layers, ascending priority, last declared wins:

@layer ghost_tokens, reset, baseline, forms, utilities;

ghost_tokens carries fully-resolved fallback values for every design token the system consumes. The browser parses :root custom properties before first paint, so every var() call resolves immediately — even before a theme sheet has loaded. This is the FOUC firewall: nothing ever renders against an empty variable.

reset erases browser inconsistencies with zero visual opinion. Every selector is wrapped in :where(), holding specificity at (0,0,0) — a single plain selector anywhere downstream outranks it without effort.

baseline carries the same :where() discipline, with one deliberate exception: :focus-visible, held at (0,1,0). That’s not an oversight. Focus rings are an accessibility mechanism, not a visual preference, and a slightly elevated specificity keeps them from being silently erased by an unrelated cascade accident downstream. Overriding it requires an explicit unlayered :focus-visible rule — a decision, not an accident.

forms is isolated from baseline on purpose, so a headless UI library can replace form normalization wholesale without touching resets or element defaults.

utilities sits last among quell-base’s own layers, which means it wins over every other quell-base rule by declaration order alone. No specificity hack required.

And above all five: unlayered rules always win. This is quell-core.css’s entire architectural leverage, and it resolves to three concrete override channels:

Channel Specificity cost Mechanism
Token override 0 Re-declare a custom property on :root, outside any layer
Layer append Selector-native Reopen a named layer to extend it
Unlayered rule Selector-native, but layer-exempt Any selector outside a layer outranks every layered rule

No !important anywhere in this chain. The override isn’t won through force — it’s won because the contract already grants unlayered rules the higher position. quell-core doesn’t fight quell-base for precedence. It was never in the same weight class.

The full contract, stated as one line: quell-base declares zero-specificity ground and stays inside its layers. quell-core sits outside every layer and wins unconditionally. quell-light — and now quell.css v2 — consumes the tokens both establish and never hardcodes a value the token system already owns.


9.3b — The Eight-Layer Model

quell-light needed one layer boundary, because it solved one problem at one scale. quell.css v2 solves for the full surface of a production system, and its cascade reflects that directly. Where quell-light’s layer contract was inherited wholesale from quell-base, v2 declares its own — eight named layers, ascending priority:

@layer theme, tokens, base, layout, components, interactive, utilities, overrides;
Layer Role
theme Browser-level rendering signal — color-scheme: dark light on :root, declared before any token exists
tokens Design tokens, system settings, variables
base Low-specificity global HTML element resets
layout Structural layout engines — Flexbox, Grid
components UI elements — cards, forms, tables
interactive Focus states, pointer and hover behavior
utilities Atomic, single-purpose structural helper classes
overrides Motion-reduction shields and fallback overrides

The ordering is the argument. theme is declared first — before tokens even exists — because color-scheme is a signal to the browser’s own rendering engine, not a design decision that depends on a token value. It has to be settled before anything else resolves. overrides is declared last on purpose: a prefers-reduced-motion shield has to be able to silence any animation declared anywhere else in the file, regardless of which layer wrote it. Layer position handles most of that job — but not all of it, and the shield actually reaches for !important on every property inside it, deliberately. Why a rule in the last-declared layer still needs that extra force is Chapter 13’s argument, not this one; for now, the point is narrower: overrides sits last because losing this particular fight is not an acceptable failure mode, and the layer order is the first, but not the only, guarantee that it won’t.

Viewport media queries are held strictly inside their owning functional layer rather than floated at the bottom of the file. A responsive rule for .card lives inside components, not in a separate breakpoints block — because a responsive override to a component is still a component-layer concern, and letting it leak outside the layer would reintroduce the exact specificity leakage @layer exists to prevent.

Read against 9.3a, the pattern repeats at a different scale: quell-base’s five layers govern the foundation — reset, baseline, forms, utility primitives. quell.css v2’s eight layers govern the system built on that foundation — tokens through overrides, with real UI in between. Same contract. Same discipline. More surface area, because there’s more system to hold.


9.4 — Scaffolding quell.css v2

Scaffolding starts with the layer declaration, and nothing else. One line, at the top of the file, before a single design token exists:

@layer theme, tokens, base, layout, components, interactive, utilities, overrides;

This is not a formality. Declaring the full layer order up front — before any layer has content — is what makes the contract enforceable from the first commit. Every rule written afterward has to choose a layer to live in. There is no default, and there is no rule that can sit outside the declared order by accident.

theme comes first and is nearly empty: one property, color-scheme, set once. tokens follows immediately, because everything downstream needs something to reference. From there, the build proceeds in the order the layers were declared — base resets before layout, layout before components, components before the interactive states that depend on them existing. The layer order isn’t just a precedence rule. It’s also a build order. You cannot meaningfully write a hover state for a card that doesn’t exist yet.

One naming decision is visible even at the scaffold stage and deserves a flag here, without solving it here: v2 adopts quell-light’s naming convention — --border-radius-*, --spacing-* — as canonical, rather than quell-base’s internal --radius-* and --space-*. quell-base keeps its own names; it lives in its own layer, and its tokens aren’t consumed directly by components. That’s a layer boundary, not an inconsistency to be embarrassed about. The full resolution — why the naming diverged in the first place, and what it costs to standardize it now instead of later — is Chapter 10’s argument, not this one. Chapter 9 scaffolds the structure. Chapter 10 fills it.


9.5 — What Changes at Scale

quell-light made a set of deliberate, load-bearing deferrals. Each of them was correct for a single-breakpoint proof of concept. None of them survive contact with a full production system, and v2 doesn’t patch around them — it makes the decision quell-light was never big enough to need.

Breakpoints. quell-light shipped one, md, because one was sufficient to prove the flex grid worked. A production system serving a diverse, modern web can’t stop at one. v2’s breakpoint scale exists in tokens, ready for the responsive layer to consume — the full system is Chapter 15’s build, not this one, but the architectural point belongs here: more breakpoints only stay maintainable because they live inside an already-declared layer contract. Without @layer, four or five breakpoints of overrides is exactly the specificity war 9.1 described. With it, each breakpoint override still has an unambiguous place to sit.

Component surface. quell-light’s component set was intentionally lean — enough to prove token consumption in practice, not a full library. v2’s components layer carries the extended set the case study needs from here forward: the same token discipline, more surface.

The JavaScript companion. quell-light.js shipped four modules, scoped tightly to what CSS genuinely couldn’t solve alone. Full quell.js scope stays deferred until quell.css v2’s component library is complete and a formal behavioral audit determines, component by component, what actually needs JavaScript. That audit doesn’t happen here — it’s the opening work of Chapter 20 — but the sequencing itself is the same architectural instinct as everything else in this chapter: don’t scope the behavior layer before the structure it depends on exists.

None of this is quell-light with more code stacked on top. It’s quell-light’s constraints lifted, one at a time, because the cascade contract underneath can now bear the weight that a handwritten discipline alone never could. That’s the whole argument of this chapter, stated plainly: the crash cost a system with no version control and no enforced structure. The rebuild — eight layers, declared once, in order — is what makes losing that argument again structurally impossible.


© 2026 Ortiz Design Studio | quell Series: system case study