Chapter 1 | The Browser’s Opinion Problem
1.1 — What the Browser Does Before You Write a Single Line
Before your stylesheet loads, the browser has already made decisions.
Not suggestions. Decisions.
A heading is large. A paragraph has margin below it. An anchor is blue and underlined. A button has a border, a background, a platform-specific radius, and a font that is almost certainly not the one you intend. A list has bullets. An input has a visible border and an even more visible focus ring — one that varies by browser, by operating system, and sometimes by the version of both.
These are not accidents. They are the browser’s implementation of the HTML specification’s presentational hints visual defaults applied to every document before any author stylesheet exists. The specification defers the exact values to the user agent. The user agent fills them in. Chrome fills them in differently than Firefox. Firefox fills them in differently than Safari. Each ships a default stylesheet — the UA stylesheet — and each makes its own interpretation of what a heading or a button or an input should look like when no one has said otherwise.
You have never seen a blank document. Every document you have ever opened in a browser already had styles applied to it.
This matters because the cascade you write does not start at line one of your stylesheet. It starts at the UA stylesheet. Your code begins in the middle of a conversation that has already been running.
The question every CSS system must answer is: what do you do with that?
1.2 — The Opinion Problem
The browser’s defaults are not neutral.
They are opinionated. They encode visual decisions — about hierarchy, about spacing, about what interactive elements look like — that belong to no particular design. They were not designed for your project. They were not designed for any project. They are the floor, not the foundation.
Here is the problem: the floor is uneven.
Chrome’s <h1> inside a <section> gets a computed font size of 1.5em. Outside a section it is 2em. This is not a bug - it is a feature, specified by the HTML living standard for outline-level heading calculation. It is also behavior that almost no developer wants, ever, and that has surprised more developers than can be counted.
Firefox and Chrome agree that a button’s default font-family is not inherit. They disagree on what it is. Safari has historically applied its own border-radius to <input> elements that no amount of border-radius: 0 will remove without appearance: none — a non-obvious fix that requires knowing the problem exists in the first place.
Margin collapsing is not a browser inconsistency. It is in the specification. It collapses adjacent block margins into a single margin equal to the larger of the two, under rules that are correct, precise, and consistently surprising.
The list goes on. It always goes on.
None of this is the browser’s fault. The browser is doing exactly what the spec says. The problem is not that the browser has opinions. The problem is that the browser has different opinions across implementations, and those opinions land directly in your cascade before your first declaration.
You can fight them one at a time, every project, every time a new browser or a new spec behavior surfaces. Or you can establish the floor yourself before anyone else does.
This is the argument for the CSS reset.
1.3 — The Reset: What It Is and What It Is Not
A CSS reset is not a solution to browser inconsistency. It is a precondition for the solution.
The reset removes the browser’s opinions from the cascade. It does not replace them with better opinions. It replaces them with nothing — or more precisely, with a known, predictable baseline that the developer chose, rather than the browser.
Eric Meyer’s Reset CSS did this bluntly. It zeroed margins, paddings, line heights, font sizes, and more across a long list of elements. Every browser got the same zero. The developer started from zero on every project.
Normalize.css took a different position. Rather than erasing browser defaults, it harmonized them. Where Chrome and Firefox disagreed, Normalize picked one answer and applied it everywhere. The floor was no longer uneven. But there was still a floor — someone else’s floor.
Both approaches have value. Both have limits.
The blunt reset creates a consistent zero, but it destroys useful browser defaults along with the problematic ones. You zero the box model and also zero list styles inside <article> elements where you wanted them. You zero heading margins and then write them back, project after project, because headings should probably have some spacing.
The normalizer keeps useful defaults but is fundamentally conservative. It cannot remove an opinion — only harmonize it. And harmonized opinions are still opinions.
Neither approach addresses the deeper question: how do you build a reset that is both predictable and overridable, without fighting specificity wars to do it?
That is the question quell-base.css was built to answer.
1.4 — The Cascade, Specificity, and Why Resets Fail Without Structure
To understand why most resets create as many problems as they solve, you need to understand how the cascade orders competing declarations.
The cascade is not a mystery. It is a priority system. When two rules target the same property on the same element, the cascade picks a winner. The rules, in descending priority:
- Declarations with
!important(author > user agent) - Unlayered author declarations
- Author
@layerdeclarations (later layers beat earlier ones) - User agent styles
Within each tier, specificity decides. A class selector (0,1,0) beats a type selector (0,0,1). An ID selector (1,0,0) beats a class. Inline styles beat everything except !important.
Here is where traditional resets break down.
A reset written with bare type selectors — h1 { font-size: 1rem; } — lands in the cascade with a specificity of (0,0,1). That sounds low. It is. But it is still higher than nothing, and it creates a floor that requires matching or exceeding specificity to override. Add enough resets and you begin the specificity inflation problem that makes large stylesheets brittle: each new override needs to be slightly more specific than the last. Eventually someone reaches for !important. Eventually that becomes two !important declarations. Eventually you have a specificity arms race in your own codebase.
The inverse problem is equally common. You write a reset rule and then write a utility class that should override it. The utility class has specificity (0,1,0). The reset rule has specificity (0,0,1). The utility wins — correctly, by specificity. But that means you could not have written the reset with a class selector, or the utility would have lost. You now have to hold these relationships in your head across every file, every team member, every future change.
This is the structural problem. A reset needs to be lower in the cascade than anything the developer writes, without relying on developer discipline to make that true. It needs to be architecturally guaranteed.
CSS Cascade Layers — @layer — were shipped in all major browsers in 2022. They change the cascade in a fundamental way: layered styles always lose to unlayered styles, regardless of specificity.
@layer reset {
h1 { font-size: 100em; } /* specificity (0,0,1) */
}
h1 { font-size: 1.5rem; } /* specificity (0,0,1) — same, but unlayered */The unlayered rule wins. Not because its specificity is higher — it is identical — but because unlayered declarations sit above all @layer declarations in the cascade priority order. A reset written inside a named layer is structurally guaranteed to lose to any developer-written style, whether that style uses a type selector, a class, or an ID.
Combine @layer with :where() — which evaluates any selector at zero specificity — and you have a reset that is both maximally safe and maximally overridable:
@layer reset {
:where(h1, h2, h3, h4, h5, h6) {
margin: 0;
font-weight: inherit;
}
}The specificity of that rule, inside that layer, is (0,0,0). Any selector you write outside the layer wins. Any selector you write inside a later layer wins. The reset has become a floor you can trust to stay below you.
This is the architecture quell-base.css is built on.
1.5 — quell-base.css: The Disciplined Answer
quell-base.css is a safety-first CSS baseline. Its design makes one thing unconditional: the developer’s styles always win.
Not by convention. Not by team agreement. By structure.
The entire file is organized into five named @layer blocks, declared in a single statement at the top:
@layer ghost_tokens, reset, baseline, forms, utilities;Layer priority is ascending. utilities beats forms. forms beats baseline. baseline beats reset. reset beats ghost_tokens. And every layer in quell-base.css is beaten, unconditionally, by any unlayered style — which means any style you write.
Layer 1 — ghost_tokens
This layer exists for one reason: to prevent a flash of unstyled content when quell-base.css loads before a theme sheet.
Every design token that the file references — every var(--color-text-primary) and var(--font-size-base) — has a resolved fallback value declared here. The browser parses :root custom properties before the first paint. If the theme sheet has not arrived yet, var() calls do not resolve to nothing. They resolve to the ghost token. The page looks right from the first frame.
This is the FOUC firewall.
Layer 2 — reset
This layer erases browser inconsistencies. Every selector is wrapped in :where(). Specificity is (0,0,0) throughout.
What reset does:
- Sets
box-sizing: border-boxon everything. Margin collapse surprises gone. - Zeroes margins and paddings on all elements.
- Sets
font-size: 100%onhtml— not16px, not1rem.100%. This preserves the user’s browser font size preference. A user who has set their browser to20pxgets that20px. The developer’s layout scales with it. - Sets
text-wrap: prettyonhtmlto prevent single-word orphan lines. - Sets
overflow-wrap: anywhereso long strings — URLs, code, hash values — wrap instead of breaking their container. - Removes
list-stylefromulandol. This is a layout reset, not a prose reset. Thebaselinelayer restores list styles insidearticle,section, and.prosecontexts where they belong. - Constrains media to
max-width: 100%. Images do not overflow containers. - Collapses table borders.
What reset does not do: apply visual opinions. No colors. No fonts. No spacing decisions. The browser’s opinions are gone. Nothing is in their place yet.
Layer 3 — baseline
This layer applies minimal, intentional visual design by consuming the ghost tokens. A body font. Heading weight and line height. Link color and hover state. Code blocks. Blockquote treatment. Table styles. ::selection color.
This is what makes quell-base.css look like something rather than nothing. But it looks like your something, because every value it applies comes from a token you can override.
One deliberate exception: :focus-visible is not wrapped in :where(). Its specificity is intentionally (0,1,0). Focus rings are a critical accessibility mechanism. A slightly elevated specificity prevents them from being silently suppressed by a cascade accident in a downstream stylesheet. To override, write :focus-visible { } in your own unlayered sheet. It will win.
Layer 4 — forms
Forms are isolated from baseline so that headless UI libraries — Radix, Shoelace, Ark — can replace this entire layer without touching the reset or the baseline visual system. The layer’s job is normalization: browsers do not inherit document font on form elements by default. quell-base.css fixes that. iOS applies rounded borders to <input> elements. quell-base.css removes them. Checkboxes and radios get accent-color set to the theme’s accent token.
No visual opinions. Everything here is normalization or accessibility default.
Layer 5 — utilities
Empty. Reserved. Downstream utility classes appended to this layer automatically win over every other layer in quell-base.css — because utilities is declared last. No specificity games required.
The cascade contract quell-base.css establishes is total and architectural:
ghost_tokens → reset → baseline → forms → utilities → your styles
(all layered) (always win)The developer does not need to know about this contract to benefit from it. They just write CSS. Their CSS wins
1.6 — quell-core.css: The Theme Layer Is Not the Reset
quell-core.css is often introduced alongside quell-base.css. This proximity leads to a misread that is worth correcting now, at the start.
quell-core.css is not a reset. It does not normalize browser behavior. It does not erase inconsistencies. That work is done. quell-base.css did it.
It lives outside any @layer. This is not an oversight — it is the mechanism. Unlayered declarations beat all layered declarations in the cascade. quell-core.css, which is loaded after quell-base.css, overrides every token in every quell-base.css layer without needing a single selector with elevated specificity.
<link rel="stylesheet" href="quell-base.css">
<link rel="stylesheet" href="quell-core.css">Load order is the cascade contract. quell-core.css arrives second. It is unlayered. It wins.
What quell-core.css contains:
@font-face declarations. Fraunces, Nunito, JetBrains Mono — loaded with font-display: swap. Fonts are declared here, in the theme layer, because type decisions are theme decisions. No other quell product declares @font-face. The theme sheet is the single source of truth for font loading.
Token overrides. quell-base.css defined the ghost_tokens floor — system UI fonts, a blue accent, neutral grays. quell-core.css replaces them with the quell theme: Nunito as the primary typeface, Fraunces as the editorial voice, #ff3d40 as the accent, a surface system built on off-white and midnight blue.
Dark mode. A @media (prefers-color-scheme: dark) block on :root swaps the semantic tokens — --color-surface, --color-text-primary, --color-border. The components never change. The tokens change. The page adapts.
Surface C components. A foundational component like .card lives here completely unlayered to win without fighting the cascade, while global layout utilities like .skip-link, .sr-only, and .truncate are safely appended to the system’s utilities layer.
The relationship between quell-base.css and quell-core.css is the cascade contract made visible:
quell-base.css quell-core.css
(layered, always loses (unlayered, always wins
to unlayered styles) over quell-base)This is not complexity. It is the cascade doing what it was designed to do, used deliberately.
A developer building on this stack starts with browser inconsistencies erased, a token-driven visual baseline in place, custom fonts loaded, and a dark mode that works without a single line of JavaScript. The floor is level. The theme is in place. The kitchen is stocked.
© 2026 Ortiz Design Studio. | quell Series: system case study