Building a Production Design System with React and Storybook 10
By Eoin Villarin, 2026-02-12, updated 2026-10-28
A React design system only pays off when teams adopt it. Here is the token pipeline, component architecture, Storybook 10 testing and release workflow I use in 2026.
How do I build a production-ready React design system with Storybook 10?
A production-ready React design system starts with design tokens in the stable DTCG format, transformed into CSS variables and TypeScript with Style Dictionary. Components are organised into primitives, patterns and sections, documented and tested in Storybook 10 with the Vitest addon, and versioned with Changesets as an npm package with React as a peer dependency.
Key takeaways
- The Design Tokens Community Group published the first stable Design Tokens Format Module, version 2025.10, in October 2025.
- Organise components in three layers, primitives, patterns and sections, where each layer imports only from layers below it.
- Storybook 10 is ESM-only and cut install size by 29 percent, according to its release notes.
- The Storybook Vitest addon turns every story into a test and runs it in real Chromium through Playwright.
- Since React 19, function components receive ref as a normal prop, so forwardRef is no longer required.
A React design system is supposed to make every team faster, yet many of the libraries I am asked to rescue do the opposite: three button variants that look identical, colours hardcoded in forty files and a Storybook nobody has opened in months. The problem is rarely React itself. It is the missing foundation underneath the components.
In this guide I walk through the architecture I use when building component libraries in 2026: design tokens in the new stable community format, a three-layer component structure, Storybook 10 for documentation and testing, and a release workflow that keeps consumers up to date without breaking their apps.
Whether you are starting from scratch or untangling an existing library, you will leave with concrete file structures, code you can copy and a practical checklist for getting other teams to adopt it.
What makes a React design system production-ready?
A production-ready system is more than a folder of shared components. It is a product with its own users (your developers and designers), its own release cycle and its own quality bar. If any of the pieces below is missing, teams will quietly work around the library instead of through it.
- A single source of truth for design decisions, expressed as tokens that both design tools and code consume.
- Accessible, composable components with typed props and predictable escape hatches.
- Living documentation where every state of every component can be seen and tested.
- Automated quality gates for interactions, accessibility and visual regressions.
- Semantic versioning and changelogs so consumers know exactly what an upgrade will do.
The rest of this article takes each of those pieces in order, starting with the one most teams skip.
How to start with design tokens, not components
Design tokens are named values for colour, typography, spacing, radius, shadow and motion. A button and a card should both reference color.action-primary rather than a hex code, so a rebrand becomes a token change instead of a risky find-and-replace across the codebase.
Use the stable DTCG token format
The Design Tokens Community Group published the first stable version of the Design Tokens Format Module (2025.10) in October 2025. It is a W3C Community Group report rather than a formal W3C standard, but it finally gives design tools and build tools a shared JSON format with theming, aliases and modern colour spaces. Files use the .tokens.json extension:
{
"color": {
"violet-600": {
"$type": "color",
"$value": { "colorSpace": "srgb", "components": [0.49, 0.23, 0.93] }
},
"action-primary": {
"$type": "color",
"$value": "{color.violet-600}"
}
},
"space": {
"md": {
"$type": "dimension",
"$value": { "value": 16, "unit": "px" }
}
}
}
Notice the alias in action-primary. Components reference semantic tokens, and only the semantic layer points at raw palette values. That one level of indirection is what makes dark mode and multi-brand theming cheap later.
Transform tokens into CSS variables and TypeScript
Style Dictionary remains the most common way to turn one token source into CSS custom properties, TypeScript constants and native platform files. Version 5 requires Node.js 22 or later and locks its reference syntax to the DTCG curly brace format, so check your CI image before upgrading. If your project uses Tailwind CSS v4, generate a CSS file with an @theme block so utility classes and components read exactly the same variables.
How to structure components so the library scales
I organise every component library into three layers. Each layer may only import from the layers below it, which keeps dependencies flowing in one direction and makes every component easy to test in isolation.
| Layer | Examples | Responsibility | May import from |
|---|---|---|---|
| Primitives | Button, Input, Dialog, Tooltip | Accessibility, keyboard behaviour and base styling from tokens | Tokens only |
| Patterns | SearchBar, FormField, DataTable | Compose primitives into reusable interaction patterns | Tokens and primitives |
| Sections | Header, ProductCard, PricingTable | Full UI blocks with layout and content slots | Tokens, primitives and patterns |
Build primitives on proven accessibility foundations
Do not hand-roll focus traps, roving tab index or dialog semantics. Headless libraries such as Radix Primitives, React Aria and Base UI (now stable at version 1 and built by people behind Radix, Floating UI and Material UI) handle keyboard and screen reader behaviour so you can concentrate on styling. My practical guide to accessibility in React covers the patterns these libraries implement for you.
Rules every component should follow
- Export a TypeScript props type for every component and compile the library with strict mode on. My TypeScript strict mode guide shows how to fix the errors in bulk.
- Accept
classNameand passrefthrough. Since React 19, function components receiverefas a normal prop, soforwardRefis no longer required. - Use a
variantprop for visual differences instead of several boolean props that can conflict. - Keep data fetching out of the library. Components receive data; applications fetch it.
What Storybook 10 brings to a React design system
Storybook is still the workshop where components are built, documented and tested in isolation. The 10.x line, first released in October 2025, is ESM-only and cut install size by 29 percent according to the Storybook 10 release notes. Later minor versions added Vite 8 support and a preview of Storybook MCP for React (10.3, March 2026), then a skills architecture for agentic workflows in 10.6 (September 2026), so AI coding agents can read your component documentation instead of guessing at props.
Write stories in CSF3 with typed args
Component Story Format 3 is still the default. Each named export is a story, args are typed from your component props, and the autodocs tag generates a documentation page from prop types and JSDoc comments:
// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react-vite';
import { expect, fn } from 'storybook/test';
import { Button } from './Button';
const meta = {
component: Button,
tags: ['autodocs'],
args: { onClick: fn() },
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: { variant: 'primary', children: 'Add to cart' },
};
export const ClickCallsHandler: Story = {
args: { children: 'Submit' },
play: async ({ args, canvas, userEvent }) => {
await userEvent.click(canvas.getByRole('button', { name: 'Submit' }));
await expect(args.onClick).toHaveBeenCalled();
},
};
Storybook 10 also includes a preview of CSF Next, which uses factory functions such as definePreview and meta.story to carry types from your configuration through every story. Because it is still marked as a preview, I use it on new internal libraries and keep CSF3 on client projects until the API is final.
How to test components with Storybook and Vitest
Since Storybook 9, testing lives in the core product. The Vitest addon turns every story into a test: each story gets a smoke test that confirms it renders, and any play function runs as an interaction test. Install it with one command:
npx storybook add @storybook/addon-vitest
The addon needs a Vite-based Storybook framework and Vitest 3 or later. It runs tests in a real Chromium browser through Playwright, which is far more accurate than a simulated DOM for components that depend on layout or browser APIs. The Vitest addon documentation covers manual setup for unusual projects.
A complete quality gate in CI looks like this:
- Interaction tests from play functions, run on every pull request.
- Accessibility checks from the a11y addon, which runs axe-core rules against every story and can fail the run on the violations you choose to enforce.
- Visual regression tests through Chromatic or another snapshot service, so a one pixel padding change in a primitive cannot ship unnoticed.
- Coverage reports generated from the same stories, showing which components have states nobody has documented.
If you sell into the EU, those accessibility checks are not optional polish. The European Accessibility Act now applies to many online stores and services, and a shared component library is the cheapest place to fix an issue once for every product.
How to version and distribute your component library
Publish the library as a private or public npm package and let Changesets manage versions. Each pull request includes a small markdown file describing the change and whether it is a patch, minor or major bump. When it merges, Changesets updates versions and writes the changelog for you.
npx changeset # describe the change and pick patch, minor or major
npx changeset version # bump versions and write CHANGELOG.md
npx changeset publish # build output is published to npm
Ship compiled JavaScript plus type declarations, never raw TypeScript source, and declare React as a peer dependency so consumers never end up with two copies of it:
{
"name": "@acme/ui",
"version": "2.3.0",
"type": "module",
"files": ["dist"],
"sideEffects": ["**/*.css"],
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./styles.css": "./dist/styles.css"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}
Build the output with Vite library mode or a dedicated bundler such as tsup. If you are rethinking the build tooling for the rest of your stack as well, my Vite vs Webpack comparison explains what changed with Vite 8 and Rolldown.
How to get teams to actually adopt your design system
Adoption is the hardest part, and it is a people problem more than a technical one. I have seen carefully engineered libraries fail simply because using them felt slower than copying a component and tweaking it.
A design system succeeds when contributing to it feels easier than working around it.
- Start with one high-traffic product team and migrate their most used screens first. Their feedback is worth more than any roadmap meeting.
- Write a migration guide for every major release, with before and after code, and provide codemods when the change is mechanical.
- Publish a contribution path: an issue template, a review rota and a clear definition of done (story, test, docs and a changeset).
- Measure usage by scanning repositories for imports from the package and for hardcoded colours that should be tokens.
- Keep designers in the loop by syncing tokens with Figma Variables through a plugin or script, so design and code never drift apart.
For smaller businesses, a full design system can be overkill. A token file, a dozen well-tested components and a Storybook is often enough, and that foundation grows into a complete system later without a rewrite.
Frequently asked questions
What is the difference between a component library and a design system?
A component library is the code: buttons, inputs, dialogs and so on. A design system is the whole agreement around that code, including design tokens, usage guidelines, accessibility rules, documentation and a release process. You can have a component library without a design system, but a React design system always contains a component library as its most visible part.
Is Storybook still worth using in 2026?
Yes. Storybook 10 combines documentation, interaction tests, accessibility checks and visual testing in one place, and recent releases add MCP and agent skills so AI coding tools can read your component docs. Lighter alternatives exist, but none match its testing integrations for a shared library that several teams depend on every day.
Should design tokens live in Figma or in code?
Treat the token JSON in your repository as the source of truth and sync it with Figma Variables through a plugin or script. Code needs versioning, review and automated builds, which Git already provides. Designers can still edit values, but every change flows through a pull request so all platform outputs are regenerated together.
How long does it take to build a design system?
A focused first version with tokens, core primitives, Storybook docs and CI tests usually takes a few weeks of dedicated work rather than a few days, especially when a designer is involved. Expanding it across several products is ongoing work, so budget maintenance time every sprint instead of treating the system as a one-off project.
Next steps for your React design system
Start small: define your tokens in the DTCG format, build five primitives on an accessible headless library, document them in Storybook 10 and wire the Vitest addon into CI. Once that loop is fast and reliable, adding new components becomes routine instead of risky.
If you want help planning or building a component library for your product, get in touch through my contact form or browse my case studies to see the websites and apps I have built for brands in Australia, the US and beyond.