quell Base v1.0.0
A safety-first CSS baseline for the modern web. quell-base.css provides browser normalization, design tokens, and minimal visual defaults in a fully overridable cascade layered architecture.
Overview
quell-base.css is structured into five named @layer blocks. Layer priority is ascending — the last declared layer wins. Unlayered styles (your project theme) always beat every layer in quell-base.css.
@layer ghost_tokens, reset, baseline, forms, utilities;
| Priority | Layer | Role |
|---|---|---|
| 1 (lowest) | ghost_tokens |
Fallback design tokens |
| 2 | reset |
Browser inconsistency erasure |
| 3 | baseline |
Minimal visual design |
| 4 | forms |
Modern form normalization |
| 5 (highest) | utilities |
Reserved for downstream use |
| — (beats all) | Unlayered / theme | Your styles always win |
Usage
Standalone Baseline: Use as a direct, drop-in reset to eliminate browser style inconsistencies and establish a predictable typographical foundation for simple HTML layouts.
With Your Project
<link rel="stylesheet" href="quell-base.css"> <!-- always loads first -->
<link rel="stylesheet" href="your-project-theme.css">
System Foundation: Use as the required first file in the quell loading order to declare global cascade layers and initialize fallback parameters before downstream theme or component sheets load.
System Foundation
<!-- system load order -->
quell-base.css > quell.core.css > quell-light.css
Override Surfaces
Three ways to override quell-base.css, in ascending power:
A. Token override — Re-declare a custom property on :root outside any @layer. Unlayered properties always win, regardless of load order. No specificity required.
:root { --color-accent: #7c3aed; }
B. Layer append — Reopen a layer in your own sheet to add or modify rules within it.
@layer baseline { :where(h1) { font-size: 2.5rem; } }
C. Unlayered rule — Any selector outside a layer beats all quell-base.css layers.
h1 { font-size: 2.5rem; }
!important is never required to override quell-base.css.
Layer 1 — ghost_tokens
Purpose
Provides fully-resolved fallback values for every design token consumed by quell-base.css. Because the browser parses :root custom properties before the first paint, all var() calls are always resolved — even if a theme sheet hasn’t loaded yet. This is the FOUC firewall.
--{category}-{variant}: {value}
- Categories:
font,color,space,size,radius,border,shadow,motion
Typography Tokens
| Token | Value | Notes |
|---|---|---|
--font-family-base |
system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif |
|
--font-family-heading |
inherit |
Inherits base by default |
--font-family-mono |
ui-monospace, 'Cascadia Code', 'Fira Code', 'Consolas', monospace |
|
--font-size-base |
1rem |
16px at browser default |
--font-size-sm |
0.875rem |
14px |
--font-size-xs |
0.75rem |
12px |
--font-weight-normal |
400 |
|
--font-weight-medium |
500 |
|
--font-weight-bold |
600 |
|
--line-height-base |
1.5 |
|
--line-height-tight |
1.2 |
Used for headings |
--line-height-loose |
1.75 |
|
--letter-spacing-tight |
-0.015em |
Used for headings |
--letter-spacing-normal |
0em |
|
--letter-spacing-wide |
0.05em |
|
Color Tokens — Neutral Scale
| Token | Value |
|---|---|
--color-neutral-900 |
#0f172a |
--color-neutral-800 |
#1e293b |
--color-neutral-700 |
#334155 |
--color-neutral-600 |
#475569 |
--color-neutral-500 |
#64748b |
--color-neutral-400 |
#94a3b8 |
--color-neutral-300 |
#cbd5e1 |
--color-neutral-200 |
#e2e8f0 |
--color-neutral-100 |
#f1f5f9 |
--color-neutral-50 |
#f8fafc |
Color Tokens — Semantic Aliases
| Token | Value | Notes |
|---|---|---|
--color-text-primary |
--color-neutral-900 |
|
--color-text-secondary |
--color-neutral-600 |
|
--color-text-muted |
--color-neutral-400 |
|
--color-text-inverse |
#ffffff |
|
--color-surface |
#ffffff |
|
--color-surface-subtle |
--color-neutral-50 |
|
--color-surface-raised |
#ffffff |
|
--color-border |
--color-neutral-200 |
|
--color-border-strong |
--color-neutral-300 |
|
--color-accent |
#0057cc |
WCAG AA on white; override freely |
--color-accent-hover |
#003fa3 |
|
--color-accent-subtle |
#dbeafe |
|
--color-focus-ring |
--color-accent |
|
Color Tokens — State Palette
| Token | Value |
|---|---|
--color-success |
#16a34a |
--color-warning |
#d97706 |
--color-error |
#dc2626 |
--color-info |
#17a2b8 |
Spacing Tokens
4px base, geometric scale.
| Token | Value | Pixels |
|---|---|---|
--space-1 |
0.25rem |
4px |
--space-2 |
0.5rem |
8px |
--space-3 |
0.75rem |
12px |
--space-4 |
1rem |
16px |
--space-5 |
1.25rem |
20px |
--space-6 |
1.5rem |
24px |
--space-8 |
2rem |
32px |
--space-10 |
2.5rem |
40px |
--space-12 |
3rem |
48px |
--space-16 |
4rem |
64px |
Size Tokens — Content Widths
| Token | Value | Pixels |
|---|---|---|
--size-content-xs |
20rem |
320px |
--size-content-sm |
30rem |
480px |
--size-content-md |
45rem |
720px |
--size-content-lg |
60rem |
960px |
--size-content-xl |
80rem |
1280px |
Border & Radius Tokens
| Token | Value | Notes |
|---|---|---|
--border-width-thin |
1px |
|
--border-width-base |
2px |
|
--radius-sm |
0.25rem |
4px |
--radius-md |
0.375rem |
6px |
--radius-lg |
0.5rem |
8px |
--radius-xl |
0.75rem |
12px |
--radius-full |
9999px |
Pill / circle |
Layer 2 — reset
| Token | Value |
|---|---|
--shadow-sm |
0 1px 2px 0 rgb(0 0 0 / 0.05) |
--shadow-md |
0 4px 6px -1px rgb(0 0 0 / 0.07), 0 2px 4px -2px rgb(0 0 0 / 0.05) |
--shadow-lg |
0 10px 15px -3px rgb(0 0 0 / 0.07), 0 4px 6px -4px rgb(0 0 0 / 0.05) |
--shadow-focus |
0 0 0 3px rgb(0 87 204 / 0.35) |
Layer 3 — baseline
| Token | Value | Notes |
|---|---|---|
--duration-instant |
50ms |
|
--duration-fast |
150ms |
|
--duration-base |
250ms |
|
--duration-slow |
400ms |
|
--ease-default |
cubic-bezier(0.4, 0, 0.2, 1) |
Material standard |
--ease-in |
cubic-bezier(0.4, 0, 1, 1) |
|
--ease-out |
cubic-bezier(0, 0, 0.2, 1) |
|
--ease-spring |
cubic-bezier(0.34, 1.56, 0.64, 1) |
Overshoot effect |
Layer 2 — reset
Purpose
Erases browser inconsistencies. Zero visual opinions. Every selector is wrapped in :where() — specificity is (0,0,0), so any plain selector in a later layer or your theme wins.
What reset does NOT do: - Set colors, fonts, or spacing — that is baseline‘s job - Normalize form appearance — that is forms’ job - Apply any visual styling
Key Rules
| Element(s) | Rule | Rationale |
|---|---|---|
*, *::before, *::after |
box-sizing: border-box, margin: 0, padding: 0 |
Prevents margin collapse surprises |
html |
font-size: 100% |
Preserves user’s browser font-size preference; never set px here |
html |
text-wrap: pretty |
Avoids single-word orphan lines in paragraphs |
html |
overflow-wrap: anywhere |
Long strings (URLs, code) wrap instead of overflowing |
html |
touch-action: manipulation |
Removes 300ms tap delay on mobile without disabling zoom |
body |
min-block-size: 100dvb |
Dynamic viewport block — safe on mobile |
h1–h6 |
text-wrap: balance |
Distributes heading text evenly across lines |
ul, ol |
list-style: none |
Reset for layout safety; restored in baseline for prose contexts |
img, picture, video, canvas, svg |
display: block; max-width: 100% |
Removes inline image gap; constrains width |
img, video |
height: auto |
Preserves intrinsic aspect ratio |
svg |
overflow: hidden |
Removes implicit space below inline SVGs |
b, strong |
font-weight: bolder |
Relative to parent; avoids hardcoding 700 |
small |
font-size: 80% |
|
sub, sup |
font-size: 75%; line-height: 0 |
Prevents line-height disruption |
a |
color: inherit; text-decoration: inherit |
Links adopt context color naturally; baseline applies actual styling |
table |
border-collapse: collapse; border-spacing: 0 |
|
th |
font-weight: inherit; text-align: start |
Logical property — RTL-safe |
hr |
height: 0; border: 0 |
Visual treatment delegated to baseline |
iframe, embed, object |
display: block; max-width: 100%; border: 0 |
|
dialog |
border: 0; padding: 0; max-width/height: 100% |
UA ships dialog with 2px border and margin |
[hidden] |
display: none !important |
Semantic attribute; must never be overridden by cascade accidents. Only !important in Quell Base |
Layer 3 — baseline
Purpose
Applies minimal, intentional visual design by consuming ghost tokens. This is what makes quell-base.css look like something beyond a bare reset.
Specificity contract: :where() throughout → (0,0,0). Exception: :focus-visible (see Focus section).
Key Rules
| Element(s) | Applied Styles | Notes |
|---|---|---|
body |
font-family, font-size, font-weight, line-height, color, background-color, font-smoothing |
Consumes base tokens |
h1–h6 |
font-family, font-weight: bold, line-height: tight, letter-spacing: tight, color: text-primary |
|
a |
color: accent, underline with color-mix opacity, text-underline-offset: 0.2em, transition on hover |
:hover darkens to accent-hover; :visited uses text-secondary |
hr |
border-block-start using border-width-thin and color-border; margin-block: space-8 |
|
code, kbd, samp, pre |
font-family: mono; font-size: sm |
|
Inline code |
Background surface-subtle, border, radius-sm, padding |
Excludes pre > code |
pre |
Dark background (neutral-900), light text (neutral-100), radius-lg, overflow-x: auto |
Inner code resets background/border |
blockquote |
Left border (border-width-base, color-border-strong), padding-inline-start: space-4, italic, text-secondary |
|
Lists inside article, section, .prose |
Restores disc / decimal with padding-inline-start: space-6 |
Reset removes styles globally; this restores for prose contexts only |
table |
width: 100%; font-size: sm |
|
th, td |
padding-block: space-2, padding-inline: space-3, text-align: start, bottom border |
|
th |
font-weight: bold, color: text-primary, background: surface-subtle |
|
::selection |
background-color: accent-subtle, color: text-primary |
Focus Visible
:focus-visible is intentionally NOT wrapped in :where(). Specificity is (0,1,0).
Why: 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, use :focus-visible { } in your own unlayered sheet.
:focus-visible {
outline: 2px solid var(--color-focus-ring);
outline-offset: var(--space-1);
border-radius: var(--radius-sm);
box-shadow: var(--shadow-focus);
}
:focus:not(:focus-visible) {
outline: none;
box-shadow: none;
}
Reduced Motion
@media (prefers-reduced-motion: reduce) {
:where(*, *::before, *::after) {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
quell-base.css uses no animations itself, but this query is present so downstream utilities can reference or build on it.
Layer 4 — forms
Purpose
Modern form normalization. Isolated from baseline so that headless UI libraries (Radix, Shoelace, Ark, etc.) can re-declare this entire layer without touching reset or baseline.
Specificity: :where() throughout → (0,0,0).
What normalization means here: - Inherit document font (browsers don’t do this for inputs by default) - Normalize cross-browser appearance quirks - Provide accessible defaults for interactive states - No visual opinions beyond what is required for usability
Key Rules
| Element(s) | Applied Styles | Notes |
|---|---|---|
button, input, optgroup, select, textarea |
font: inherit; color: inherit; margin: 0 |
Browsers don’t inherit font on form elements by default |
button, [type="button/reset/submit"] |
appearance: none; cursor: pointer; border: 0; background: transparent; padding: 0; touch-action: manipulation; user-select: none |
Removes platform chrome; eliminates 300ms iOS tap delay |
Text inputs, select, textarea |
appearance: none; border-radius: 0 |
Overrides iOS rounded input style |
textarea |
resize: vertical; min-block-size: 3lh; overflow: auto |
3lh = 3 line heights (2023+ unit); gracefully degrades |
select |
max-width: 100% |
Restores truncated text on some platforms |
input[type="checkbox"], input[type="radio"] |
accent-color: accent; width/height: 1rem; cursor: pointer; flex-shrink: 0 |
Brand color for checked state without custom widget engineering |
input[type="radio"] |
border-radius: full |
|
input[type="range"] |
accent-color: accent; cursor: pointer; width: 100% |
|
input[type="color"] |
cursor: pointer; block-size: 2rem; inline-size: 3rem; padding: space-1; border-radius: sm |
|
fieldset |
border: 0; padding: 0; margin: 0; min-inline-size: 0 |
min-inline-size: 0 is a critical fix — browsers default to min-content, causing flex/grid overflow |
legend |
padding: 0; max-width: 100% |
|
label |
cursor: pointer |
|
progress, meter |
vertical-align: middle; accent-color: accent |
|
output |
display: block; font-size: sm; color: text-secondary |
|
[disabled], [aria-disabled="true"] |
cursor: not-allowed; opacity: 0.55 |
|
fieldset[disabled] children |
cursor: not-allowed; pointer-events: none |
Fieldset disables children but disabled doesn’t cascade; fieldset is targeted directly |
input[readonly], textarea[readonly] |
cursor: default |
|
:user-invalid inputs |
accent-color: error |
:user-invalid fires only after user interaction (2023+); preferred over :invalid to avoid false positives on page load |
input[type="search"] |
Hides WebKit :-webkit-search-decoration and cancel-button |
Removes native ‘x’ clear button in WebKit |
input[type="number"] |
appearance: textfield; hides spin buttons |
Spinner arrows are rarely useful and always inconsistent |
Layer 5 — utilities
Purpose
Reserved slot. Intentionally empty in quell-base.css core.
For downstream systems
Atomic utility classes appended to this layer automatically win over all lower quell-base.css layers, because layer priority equals declaration order. No specificity hacks needed.
@layer utilities {
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}
System Configuration
color-scheme: light dark is set on :root inside ghost_tokens. This tells the browser to use the user’s OS preference for light/dark mode. Override with an unlayered rule:
:root { color-scheme: light; } /* force light mode */
Specificity Summary
| Layer | Selector wrapping | Specificity |
|---|---|---|
ghost_tokens |
:root |
(0,1,0) |
reset |
:where() throughout |
(0,0,0) |
baseline |
:where() throughout |
(0,0,0) |
baseline — :focus-visible |
No :where() — intentional |
(0,1,0) |
forms |
:where() throughout |
(0,0,0) |
utilities |
Up to downstream author | varies |
| Your unlayered theme | Any selector | Always wins |
Key Design Decisions
- Zero Explicit Overrides Eliminates !important from layout styling. The flag is reserved exclusively for [hidden] elements and prefers-reduced-motion states to secure accessibility barriers and browser primitives.
- FOUC firewall —
ghost_tokensensures novar()is ever empty before a theme loads. - Zero specificity resets —
:where()wrapping throughoutresetandbaselinemeans a single unstyled selector in your theme or utility sheet always wins. - Forms are isolated — The
formslayer can be fully replaced by headless UI libraries without affectingresetorbaseline. - Accessibility by default — Focus visibility, reduced motion,
accent-color,:user-invalidover:invalid, andtouch-action: manipulationare all built in. - Logical properties — RTL-safe properties (
text-align: start,padding-inline-start,border-inline-start, etc.) are used throughout.
© 2026 Ortiz Design Studio. | quell system documentation.