Designing with tokens
How every surface in this boilerplate stays on-theme — one set of semantic tokens, no hard-coded colors, and components that restyle themselves when the theme changes.
Every color in this boilerplate is a semantic token: background, foreground, muted, primary, border. Components never name a color — they name a role, and the theme decides what that role looks like.
That one rule is why swapping the entire look of the app is a change to a single CSS file.
Why semantic tokens
Hard-coded colors rot. The first text-gray-500 looks harmless; six months later there are forty of them, dark mode ships, and half of them are wrong in ways nobody notices until a screenshot lands in a customer deck.
Tokens invert the problem. A component says text-muted-foreground — "de-emphasized text" — and both themes carry their own answer for what that means.
The palette lives in one file
packages/ui/src/styles/globals.css defines every token twice: once under :root for light, once under .dark. Nothing else in the repo declares a color.
Components stay ignorant
A card is bg-card text-card-foreground. It has no idea whether it renders on marketing white or dashboard black, and that ignorance is the feature.
Radius and spacing follow the same rule
The corner radius scale derives from a single --radius variable. Want a sharper product? Change one line and every button, card, and dialog follows.
What this buys you
- Re-theming is a config change, not a migration.
- Dark mode is correct by construction, not by audit.
- New components inherit the design language for free — if it only uses tokens, it cannot be off-brand.
The discipline costs nothing when you start and is nearly impossible to retrofit. Start with it.