"Design system" sounds like something for companies with a dedicated team and a documentation site. But a small team needs one more, not less: with two designers and three developers, every inconsistency is fixed by hand, again and again. The trick is to build a small one, in the right order.
What a small design system actually is
Three layers, and you only need the first two to start:
- Tokens — named decisions: colours, type sizes, spacing, radii, shadows, motion.
- Components — the reusable pieces built from those tokens: buttons, inputs, cards.
- Patterns and guidelines — how components combine: forms, empty states, page layouts. Written down later, when you notice yourselves repeating explanations.
Most teams start at layer two and build a beautiful button library on top of 37 slightly different greys. Start with tokens.
Step 1: Tokens
Colour
Define two tiers. Primitive tokens are the palette; semantic tokens say what a colour is for:
:root {
/* Primitives */
--blue-500: #0777d2;
--blue-600: #0563b0;
--gray-50: #f8f9fa;
--gray-900: #111418;
--red-600: #d92d20;
/* Semantic — components only use these */
--color-bg: #ffffff;
--color-surface: var(--gray-50);
--color-text: var(--gray-900);
--color-text-muted: #5b6370;
--color-primary: var(--blue-500);
--color-primary-hover: var(--blue-600);
--color-danger: var(--red-600);
--color-border: #e3e6ea;
}
[data-theme='dark'] {
--color-bg: #0d1117;
--color-surface: #161b22;
--color-text: #e6edf3;
--color-border: #30363d;
}Components reference semantic tokens only. That's what makes dark mode, a rebrand or a white-label client a change to one file.
Check contrast while you're here: body text needs at least 4.5:1 against its background (WCAG AA), large text 3:1.
Typography
Pick one or two families and a scale of six to eight sizes. A modular scale (each step about 1.2–1.25× the previous) keeps the rhythm consistent:
:root {
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.25rem;
--text-xl: 1.5rem;
--text-2xl: 2rem;
--text-3xl: 2.75rem;
--leading-tight: 1.15;
--leading-normal: 1.6;
}Spacing, radius, shadow, motion
A 4- or 8-pixel spacing scale (4, 8, 12, 16, 24, 32, 48, 64), two or three radii, two or three shadows, and two or three durations with one easing curve. Every value that isn't on a scale is a decision someone has to make again.
In Tailwind CSS
If you use Tailwind, the theme is your token file. Point it at the CSS variables so the same tokens drive both:
export default {
theme: {
extend: {
colors: {
bg: 'var(--color-bg)',
surface: 'var(--color-surface)',
text: 'var(--color-text)',
muted: 'var(--color-text-muted)',
primary: { DEFAULT: 'var(--color-primary)', hover: 'var(--color-primary-hover)' },
danger: 'var(--color-danger)',
border: 'var(--color-border)',
},
},
},
}Step 2: The first ten components
These cover most product screens:
- Button — primary, secondary, ghost, danger; sizes; loading and disabled states
- Text input — with label, hint and error message
- Select / combobox
- Checkbox, radio and switch
- Card — the container for almost everything
- Modal / dialog — with focus trapping and Escape to close
- Toast — for confirmations and errors
- Tabs
- Badge / tag
- Empty state — icon, one line of text, one action
Don't build accessibility from scratch: headless libraries such as Radix UI, React Aria or Headless UI handle focus, keyboard navigation and ARIA, and you style them with your tokens. shadcn/ui is a popular starting point that copies Radix-based components into your own codebase, where you own them.
Every component needs its states
The difference between a demo and a product is states. For each component, design and build: default, hover, focus (visible!), active, disabled, loading, error, and empty. Focus states are the ones most often forgotten — and the ones keyboard users depend on.
Step 3: Keep Figma and code in sync
- Same names in both. If Figma calls it
color/text/mutedand code calls it--gray-500, they will drift. Use Figma Variables with the semantic names from your token file. - One source of truth for values. Small teams can simply agree that code is the source, and update Figma variables when tokens change. Larger teams export variables to JSON and generate CSS from them (Style Dictionary or Tokens Studio).
- Components in Figma mirror component props in code. A Figma variant
size=sm, variant=secondaryshould map to<Button size="sm" variant="secondary">.
Step 4: Write down only what you repeat
Documentation doesn't need to be a website. A single page in your repository with the token list, a screenshot of each component and a few rules ("one primary button per screen", "destructive actions ask for confirmation") is enough. Add a rule every time you explain the same thing twice in a review.
Mistakes I see most often
- Too many variants. Five button styles nobody can tell apart. Start with three.
- Hard-coded values sneaking back in. A lint rule or a code-review checklist item ("no hex values outside
tokens.css") keeps the system honest. - Designing components no screen needs yet. Build components when the second screen needs them, not before.
- Treating the system as finished. It's a product with your own team as users. Revisit it every few months.
How long does this take?
For a small product, a first version — tokens, ten components with states, Figma variables and a one-page guide — takes roughly one to three weeks of combined design and development time. It pays back the first time you build a new screen in an afternoon, or switch on dark mode in a day.
Starting from zero, without a logo or brand yet? Do that first — see the brand identity a startup needs at launch.

