Building a Design System with React, TypeScript, and Storybook

A design system is the single source of truth for your product's UI. It ensures visual consistency across teams, accelerates development, and reduces the gap between design and implementation. Building one with React, TypeScript, and Storybook gives you a type-safe, documented, and testable component library.
Component Architecture: Atomic Design Principles
Organize components by complexity using the atomic design hierarchy — atoms, molecules, organisms, templates, and pages. This structure makes it clear where each component belongs and how complex it should be.
// atoms/Button/Button.tsx — the smallest reusable unit
import { type ButtonHTMLAttributes, forwardRef } from 'react'
import { type VariantProps, cva } from 'class-variance-authority'
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
primary: 'bg-brand text-white hover:bg-brand-dark',
secondary: 'bg-surface text-foreground border hover:bg-muted',
ghost: 'hover:bg-muted text-foreground',
destructive: 'bg-red-600 text-white hover:bg-red-700',
},
size: {
sm: 'h-8 px-3 text-xs',
md: 'h-10 px-4 text-sm',
lg: 'h-12 px-6 text-base',
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
}
)
export interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => {
return (
<button
className={buttonVariants({ variant, size, className })}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = 'Button'
The class-variance-authority library types the variant props, so consumers get autocomplete for variant and size. The forwardRef pattern ensures the component works with form libraries and tooltips.
Theming with CSS Custom Properties
Design tokens belong in CSS custom properties, not in JavaScript. This allows theming without JavaScript overhead and enables runtime theme switching.
/* tokens.css */
:root {
--color-brand: #6c5ce7;
--color-brand-dark: #5a4bd1;
--color-foreground: #0f172a;
--color-background: #ffffff;
--color-muted: #f1f5f9;
--color-surface: #f8fafc;
--color-border: #e2e8f0;
--radius-sm: 0.375rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
--font-sans: 'Inter', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
}
.dark {
--color-foreground: #f8fafc;
--color-background: #0f172a;
--color-muted: #1e293b;
--color-surface: #1e293b;
--color-border: #334155;
}
// Components reference tokens as CSS variables, not raw values
const cardVariants = cva('rounded-lg border bg-[var(--color-surface)] p-4 shadow-sm')
This approach lets you create a dark theme, high-contrast theme, or brand-specific theme by changing CSS variables — no JavaScript state management needed.
Storybook Documentation and Testing
Storybook serves as both a development environment and living documentation. Each component gets stories covering its states, variants, and edge cases.
// Button.stories.ts
import type { Meta, StoryObj } from '@storybook/react'
import { Button } from './Button'
const meta: Meta<typeof Button> = {
title: 'Atoms/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'ghost', 'destructive'],
},
size: { control: 'select', options: ['sm', 'md', 'lg'] },
disabled: { control: 'boolean' },
},
}
export default meta
type Story = StoryObj<typeof Button>
export const Primary: Story = {
args: { variant: 'primary', children: 'Click me' },
}
export const Secondary: Story = {
args: { variant: 'secondary', children: 'Cancel' },
}
export const Destructive: Story = {
args: { variant: 'destructive', children: 'Delete' },
}
export const Disabled: Story = {
args: { disabled: true, children: 'Disabled' },
}
export const WithIcon: Story = {
args: { children: '← Back', variant: 'ghost' },
}
Add an accessibility addon (@storybook/addon-a11y) to catch contrast ratio issues, missing ARIA labels, and keyboard navigation problems during development.
Package Structure and Distribution
Publish your design system as a private npm package. Structure the repository so consuming projects only import what they need.
packages/ui/
├── src/
│ ├── atoms/
│ │ ├── Button/
│ │ │ ├── Button.tsx
│ │ │ └── Button.stories.ts
│ │ └── Icon/
│ │ ├── Icon.tsx
│ │ └── Icon.stories.ts
│ ├── molecules/
│ │ ├── Card/
│ │ └── FormField/
│ ├── organisms/
│ │ ├── Header/
│ │ └── DataTable/
│ ├── tokens/
│ │ ├── colors.css
│ │ ├── typography.css
│ │ └── spacing.css
│ └── index.ts
├── package.json
└── tsconfig.json
Using exports in package.json enables deep imports — consumers import only the components they use, keeping bundle sizes small.
Building a design system is an investment that compounds over time — every new product or feature benefits from the existing component library. At SoniNow, we help teams design and implement scalable design systems with React, TypeScript, and Storybook that accelerate development and ensure visual consistency.
Ready to build your design system? Contact SoniNow and let us help you create a component library that scales with your product.
Related Insights

Building Accessible React Applications: WCAG 2.2 Compliance Guide
A guide to building WCAG 2.2 compliant React applications including semantic HTML, ARIA attributes, keyboard navigation, focus management, and automated accessibility testing.

Code Splitting and Lazy Loading in React: Performance Optimization Guide
A comprehensive guide to code splitting and lazy loading in React applications including React.lazy, Suspense, route-based splitting, and component-level chunking.

Color Theory for Web Design: Accessibility, Branding, and Visual Hierarchy
A guide to color in web design including contrast ratios, WCAG compliance, color psychology, accessible palettes, and maintaining brand consistency.