
I've worked on codebases where every button was slightly different — different padding, different font size, different border radius. It was chaos. Nobody meant for it to happen. It just did, slowly, one rushed feature at a time. A design system is how you fight that drift.
Design tokens are the foundation — your colors, spacing scale, type sizes, border radii, shadows. Before you build a single component, define your tokens. They become the vocabulary everything else speaks in.
In Tailwind, this lives in your config. In Figma, it's your variable library. The key is that both your design tool and your codebase share the same values.
Primitives are your lowest-level components — Button, Input, Badge, Card, Typography. They should be dumb and flexible. No business logic, no hardcoded content. Just props and styling.
I usually start with shadcn/ui as a base and then extend it to match the project's brand. This gives me accessible, well-tested primitives without rebuilding from zero.
A design system nobody knows how to use is just extra code. Documentation doesn't have to be a separate project — start with a simple Storybook setup or even a dedicated `/components` page in your app.
For every component I build, I note: what it's for, what props it accepts, what it shouldn't be used for, and an example. Future me (and my teammates) always thank present me for this.
The best design systems make the right choice the easy choice. If your Button component handles all the variants, developers have no reason to write one-off styles. The system should feel like a guardrail, not a cage.
Lint rules, component exports, and clear naming go a long way. When someone joins your team, they should be able to follow the pattern within hours.
A design system isn't a destination — it's a living codebase. The teams who treat it that way ship better products, faster. Start small, stay consistent, and grow it with the product.