Design tokens: one file that runs the whole site
Why every colour, spacing step and corner radius lives in _tokens.scss — and why changing this site's entire visual direction only touched two files.
Hardcoding a handful of hex values is fine while the site is a single page. Once there is a blog, a games section and a tools section — times two themes — hardcoding starts biting back.
One source of truth
$light: (
'bg': #f5f1e8,
'text': #14120f,
'accent': #ff4a17,
'ink-2': #1b2a6b,
);
@mixin palette($map) {
@each $name, $value in $map {
--c-#{$name}: #{$value};
}
}
The rule is blunt: no hex value may appear outside this file. The moment you want to nudge a colour somewhere, you change the token rather than the component — otherwise one corner of the dark theme stays wrong and you do not find out until a reader does.
It is an SCSS map rather than plain custom properties because a re-skin rarely stops at colour. Radii, stroke widths and shadows move too, and those do not fit into one or two variables.
Re-skinning touches two files
I recently changed this site’s visual direction. The work was:
- rewrite both palettes in
settings/_tokens.scss; - rewrite the
card-shellmixin intools/_mixins.scss.
Not a single component or page changed. The new direction calls for square corners, 3px strokes and hard offset shadows, and all of that lives at the token and mixin layer:
--radius-sm: 0;
--radius-md: 0;
--radius-lg: 0;
--border-width: 3px;
--shadow-ink: 4px 4px 0 var(--c-border);
That last line is the whole trick. The blur radius is 0, so the shadow degenerates into a flat offset block — like two screens that were not quite aligned. It reads nothing like 0 4px 16px rgba(0,0,0,.1), and the price of getting there was one variable.
Fluid type
clamp() lets type scale smoothly between 320px and 1280px instead of a pile of breakpoints:
--step-0: clamp(1rem, 0.96rem + 0.2vw, 1.125rem);
--step-4: clamp(2.074rem, 1.82rem + 1.27vw, 2.74rem);
The middle expression is what matters: a little viewport unit makes the curve take a different slope at each end, so it moves quickly on small screens and flattens out on large ones. That is exactly how type should behave.
Name by role, not by colour
--c-accent, not --c-orange. --c-text-muted, not --c-gray-600. A name that describes purpose survives a palette swap without touching a line of component code.
There are counterexamples. The new direction is built from two inks, and the second one is --c-ink-2 — a name describing its role (the second screen), not its colour. Today it is ink blue; in the dark theme it is cyan. Calling it --c-blue would have been a lie half the time.
These tokens now carry three very different kinds of page. Without them I would probably have given up on a consistent look long ago.

Comments
…