Everyday Programmer
Systems

How I Start a Design System

A practical starting point for building a design system that survives real product work.

5 min readdesign systems, frontend systems, tokens

Design systems usually do not fail because the team forgot to make a button. They fail because the first version tries to be a complete product before it has earned trust.

The better starting point is smaller: document the decisions the team already repeats, turn those decisions into reusable primitives, and make the next product surface easier to build than the last one.

Audit the repeated decisions

Before I create components, I look for decisions that keep reappearing in product code. The best candidates are boring: page shells, section spacing, button hierarchy, form help text, empty states, and code examples.

A simple audit note can be enough:

audit.md
md
 1  # Product surface audit
 2  
 3  - Marketing pages use three different max widths
 4  - Form labels switch between sentence and title case
 5  - Empty states repeat the same structure with different spacing
 6  - Code examples need a shared header and copy affordance

Once the repeats are visible, the first system work becomes easier to sequence.

Define tokens before themes

I like to begin with semantic tokens because they force a conversation about intent. A token like bg.surface communicates more than a palette value. It tells designers and engineers where the value belongs.

tokens.ts
ts
export const surfaceTokens = {
  canvas: "bg.canvas",
  surface: "bg.surface",
  subtle: "bg.subtle",
} as const;

That indirection becomes valuable when the site needs dark mode, seasonal campaigns, or a product-specific theme.

Ship one vertical slice

The first release should prove the system can support real work. I usually choose one complete slice: article cards, article pages, or forms. A vertical slice exercises tokens, layout, components, content, and accessibility together.

Write usage guidance next to the component

A component without guidance becomes a guessing game. I prefer short notes that explain when to use the component, when not to use it, and which variants are safe.

For example, a callout can have production-oriented rules:

Build components in isolation

I never start a component without a Storybook story. Building in isolation forces every component to stand on its own — with clear props, clear states, and no accidental dependence on parent layout.

Button.stories.ts
ts
 1  import type { Meta, StoryObj } from "@storybook/react";
 2  import { Button } from "./Button";
 3  
 4  const meta: Meta<typeof Button> = {
 5    component: Button,
 6    argTypes: { onClick: { action: "clicked" } },
 7  };
 8  
 9  export default meta;
10  
11  type Story = StoryObj<typeof Button>;
12  
13  export const Solid: Story = { args: { variant: "solid", children: "Save" } };
14  export const Subtle: Story = { args: { variant: "subtle", children: "Cancel" } };
15  export const Ghost: Story = { args: { variant: "ghost", children: "Learn more" } };

Storybook stories are not extra work — they are the fastest way to exercise every variant and state before the component lands in a real page.

Catch visual regressions with Chromatic

Once stories exist, visual testing becomes automatic. Chromatic takes a screenshot of every story on every PR and diffs it against the last approved baseline.

The workflow I teach every team:

  1. Build the component in Storybook.
  2. Push to a branch — Chromatic snapshots every story.
  3. Review the visual diff in the PR.
  4. Approve intentional changes, fix accidental ones.

That loop turns component review from a guessing game into a visual checkpoint.

Keep the system close to product work

The design system should not become a separate universe. The fastest feedback comes from using it in the product immediately, then folding the lessons back into the system.

Systems Design Systems

Keep reading

Related articles