The problem: “global tweaks” don’t stay global
I used to treat “global” CSS changes like harmless housekeeping. Bump the base font-size. Nudge spacing. Round corners a bit more. Then I’d refresh the site and immediately feel it: buttons got chunky, cards got cramped, modals looked like they shipped from a different product.
The frustrating part is the drift. Typography, spacing, radius, and shadows are supposed to feel like they belong to the same system— but we often change them independently. A font change doesn’t automatically rebalance padding. New radii don’t automatically harmonize with shadows. And the hidden cost is always the same: manual retuning across dozens of components.
Why components drift out of proportion
- Font-size changes break the relationship between text and the spacing around it.
- Mobile and desktop need different “density,” but most components hardcode one set of numbers.
- Your design “feel” becomes fragile: small tweaks cause big inconsistency.
The Atomic Hack in one sentence
Set one scale variable (--ui-scale), derive a tiny token set from it (space/type/radius/shadow), and make components consume tokens—not raw numbers.
One scale variable
- You change
--ui-scaleonce. - Everything else is computed with
calc(). - Components stay proportional because they all reference the same source of truth.
The ~5 lines of CSS (core snippet)
This is the core. It’s intentionally small. You can expand later, but you don’t need a “design system rewrite” to start.
:root{
--ui-scale: 1; /* one knob */
--space: calc(1rem * var(--ui-scale));
--text: calc(1rem * var(--ui-scale));
--radius: calc(.75rem * var(--ui-scale));
--shadow: 0 calc(8px * var(--ui-scale)) calc(24px * var(--ui-scale)) rgb(0 0 0 / .12);
}
That’s the hack: one variable drives multiple “token families.” When you tighten the scale, your type gets slightly smaller, your padding gets tighter, your corners get subtly sharper, and your shadows don’t suddenly feel too heavy.
Make it fluid: clamp() for scale that follows the viewport
You can keep --ui-scale fixed per breakpoint. Or you can let it move smoothly with the viewport while staying in a safe range.
This is the pattern I reach for when I want the UI to breathe on larger screens without redesigning anything.
A safe clamp() pattern
:root{
--ui-scale: clamp(.92, .85 + .3vw, 1.08);
}
What to clamp (and what not to)
- Clamp the scale. The min prevents tiny UI on small phones; the max prevents comically large UI.
- Watch line length. Fluid type can push paragraphs into “too wide to read” territory if the layout also expands.
- Don’t blindly scale everything. Some things (like hairline borders) shouldn’t grow 1:1 forever.
Apply tokens everywhere (without rewriting your CSS)
The migration trick is: don’t convert the whole codebase. Pick 2–3 “surface” components you touch constantly. Once they’re token-based, everything else can move over gradually.
Start with 3 surfaces
- Buttons: padding + font-size + radius.
- Cards: padding + gap + radius + shadow.
- Forms/modals: spacing hierarchy (header/body/footer) so density changes feel intentional.
Minimal example: components consuming the derived tokens.
/* components */
.btn{
font-size: var(--text);
padding: calc(.6 * var(--space)) calc(1.0 * var(--space));
border-radius: var(--radius);
}
.card{
padding: calc(1.25 * var(--space));
border-radius: calc(1.1 * var(--radius));
box-shadow: var(--shadow);
}
Common failure modes (and quick fixes)
The “everything scales, including the wrong things” issue
- Borders: don’t scale hairlines forever. Consider pinning borders to
1px(or a narrow range) even when the UI grows. - Headings: cap big display sizes. It’s fine for body text + spacing to scale, while
h1growth is limited. - Shadows: scaling blur/spread is good, but keep opacity stable (or it can look “muddy” at large sizes).
Quick checklist
| Step | What you change | What you verify |
|---|---|---|
| Tokenize | Space / type / radius first | Components reference var(--space), var(--text), var(--radius) |
| Migrate | Replace raw px in 3 key components | UI stays consistent at --ui-scale: .92 and 1.06 |
| Scale strategy | Add clamp() or 2 breakpoints |
No tiny UI on phones; no huge UI on wide monitors |
| Sanity test | Phone, laptop, large display | Tap targets, heading rhythm, paragraph readability |