How to start a design system without over-engineering it
You do not need a hundred-component library on day one. Here is the minimum viable system that pays for itself immediately.
Design Systems
Tokens are the contract between design and engineering. What they are, how to name them, and how to keep them from turning into a mess.
A design token is a named design decision. Instead of a raw value like #3A3AFF living in a hundred files, you give it a name, and everything references the name. Change the value once and the whole product updates. That single idea is the foundation of every design system that scales.
This guide covers what tokens are, how to layer and name them so they survive a redesign, how to theme with them, and the mistakes that quietly turn a token system into a second mess.
The idea
Think of the difference between hard-coding a color and referencing a variable. The hard-coded value is a decision made in one place, invisible to the rest of the system. The token is the same decision, made once, visible, and reusable. When the brand blue changes, you do not hunt through files. You change the token.
The payoff compounds. Consistency stops depending on discipline and starts being structural. Theming becomes trivial. And the vocabulary of the token names becomes a shared language between design and engineering, which is often the real win.

Written by
Jayesh Velossa
Founder & Creative Director
Most token systems fail because they have one flat list, so every name is either a meaningless raw value or an over-specific one-off. Layers fix this.
Primitive tokens are the raw palette: blue-500, gray-900, space-4. They carry no meaning, only value. Semantic tokens describe intent: surface-0, fg-strong, accent, border-faint. Your UI references these, never the primitives, so intent survives when the palette changes. Component tokens are the optional third layer for a specific component that needs its own hook, like button-bg, and they reference semantics.
The rule that keeps it clean: UI references semantics, semantics reference primitives, and you almost never reach past a layer.
The one rule
Reference semantic tokens in your interface, not primitives. surface-0 survives a rebrand; blue-500 does not. If a component hard-codes a primitive, intent is lost the next time the palette shifts.
Good token names describe role, not appearance. surface-0 tells you where it goes. off-white tells you what it looks like today, which is exactly the thing most likely to change. Name by intent and the system reads like documentation.
Keep the semantic set small and memorable. A dozen surfaces, foregrounds, borders, and accents that a designer can hold in their head beat a hundred no one remembers. Expand only when a real decision needs a name, never speculatively.
Once your UI references semantics, a theme is just a different mapping of semantic to value. Light and dark stop being two designs and become one design with two token sets. Define the semantics once, provide values per theme, and every screen adapts without touching a component.
:root, [data-theme="dark"] {
--surface-0: #07070C; /* page background */
--fg-strong: #FAFAFA; /* max-emphasis text */
--accent: #3A3AFF;
}
[data-theme="light"] {
--surface-0: #FFFFFF;
--fg-strong: #09090C;
--accent: #3A3AFF;
}
/* UI references the semantic name, never the raw value */
.card { background: var(--surface-0); color: var(--fg-strong); }
Too many tokens too early: a system no one can remember is not used. Naming by appearance: red-error becomes a lie the day the error color changes. Skipping the semantic layer: hard-coding primitives in components removes the whole benefit. And no owner: tokens drift the moment nobody reviews additions.
Start with a small, well-named semantic set, layer it correctly, theme through it, and let it grow by real demand. That is a token system that stays an asset instead of becoming the next thing to clean up.