Storybook is an open-source tool for developing UI components in isolation. It runs alongside your app — not inside it — and gives you a dedicated sandbox where you can render every component in every state without needing to navigate your actual application.
Think of it as a living component library: every component has a page, every state has a preview, and designers and developers can browse, test, and review the entire UI without running the full app.
For design systems, Storybook is essential. It is the place where your token system, components, and documentation come together into a single, browsable reference.
Storybook has a one-command setup that detects your framework and configures everything automatically.
npx storybook@latest initThis will detect that you are using Next.js and install the correct dependencies. Once complete, start Storybook with:
npm run storybookStorybook will open at http://localhost:6006 in your browser.
Warning: You may see peer dependency warnings during installation. This is normal with Next.js projects. If Storybook starts and renders your stories, you can safely ignore these warnings. If it fails to start, try deleting
node_modulesand runningnpm installagain.
By default, Storybook does not load your Tailwind styles. You need to import your global CSS in the Storybook preview configuration.
// .storybook/preview.ts
import type { Preview } from "@storybook/react"
import "../app/globals.css"
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
}
export default previewThe key line is import "../app/globals.css" — this loads your Tailwind base styles, your custom CSS variables, and your brand token definitions into Storybook.
A story is a single state of a component. Each component gets a .stories.tsx file that lives next to it. Stories define the different ways a component can be rendered.
// components/ui/Button.stories.tsx
import type { Meta, StoryObj } from "@storybook/react"
import { Button } from "./button"
const meta: Meta<typeof Button> = {
title: "UI/Button",
component: Button,
tags: ["autodocs"],
argTypes: {
variant: {
control: "select",
options: ["default", "destructive", "outline", "secondary", "ghost", "link"],
},
size: {
control: "select",
options: ["default", "sm", "lg", "icon"],
},
},
}
export default meta
type Story = StoryObj<typeof Button>
// Individual stories
export const Default: Story = {
args: {
children: "Button",
variant: "default",
},
}
export const Destructive: Story = {
args: {
children: "Delete",
variant: "destructive",
},
}
export const Outline: Story = {
args: {
children: "Outline",
variant: "outline",
},
}
export const Secondary: Story = {
args: {
children: "Secondary",
variant: "secondary",
},
}
export const Ghost: Story = {
args: {
children: "Ghost",
variant: "ghost",
},
}
export const Link: Story = {
args: {
children: "Link",
variant: "link",
},
}
export const Small: Story = {
args: {
children: "Small",
size: "sm",
},
}
export const Large: Story = {
args: {
children: "Large",
size: "lg",
},
}
// Composite story showing all variants
export const AllVariants: Story = {
render: () => (
<div className="flex flex-wrap gap-4">
<Button variant="default">Default</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="outline">Outline</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
</div>
),
}// components/ui/Input.stories.tsx
import type { Meta, StoryObj } from "@storybook/react"
import { Input } from "./input"
const meta: Meta<typeof Input> = {
title: "UI/Input",
component: Input,
tags: ["autodocs"],
}
export default meta
type Story = StoryObj<typeof Input>
export const Default: Story = {
args: {
placeholder: "Enter your email...",
},
}
export const WithValue: Story = {
args: {
value: "hello@example.com",
readOnly: true,
},
}
export const Disabled: Story = {
args: {
placeholder: "Disabled input",
disabled: true,
},
}
export const WithLabel: Story = {
render: () => (
<div className="space-y-2">
<label htmlFor="email" className="text-sm font-medium">
Email
</label>
<Input id="email" type="email" placeholder="hello@example.com" />
</div>
),
}
export const Error: Story = {
render: () => (
<div className="space-y-2">
<label htmlFor="email-error" className="text-sm font-medium">
Email
</label>
<Input
id="email-error"
type="email"
placeholder="hello@example.com"
className="border-red-500 focus-visible:ring-red-500"
/>
<p className="text-sm text-red-500">Please enter a valid email address.</p>
</div>
),
}The title field in your meta object controls where the story appears in the Storybook sidebar. Use slashes to create a hierarchy.
// Flat
title: "Button"
// Grouped under "UI"
title: "UI/Button"
// Nested deeper
title: "UI/Forms/Input"
title: "UI/Forms/Select"
title: "UI/Forms/Textarea"
// Separate section for patterns
title: "Patterns/Card Grid"
title: "Patterns/Hero Section"
// Brand-specific
title: "Brands/Midnight/Button"
title: "Brands/Ocean/Button"Decorators let you wrap every story with a provider, layout, or context. This is essential for multi-brand design systems — you can wrap stories with your BrandProvider to preview components in each brand.
// .storybook/preview.ts
import type { Preview } from "@storybook/react"
import { BrandProvider } from "../components/brand-provider"
import "../app/globals.css"
const preview: Preview = {
decorators: [
(Story) => (
<BrandProvider defaultBrand="midnight">
<div className="p-8">
<Story />
</div>
</BrandProvider>
),
],
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
}
export default previewYou can also add decorators to individual stories or story files:
// In a specific story file
const meta: Meta<typeof Button> = {
title: "UI/Button",
component: Button,
decorators: [
(Story) => (
<div className="bg-gray-100 p-8 rounded-lg">
<Story />
</div>
),
],
}A deployed Storybook gives your entire team — designers, developers, PMs — a shared reference for every component. There are two main options.
Build Storybook as a static site and deploy it to Vercel.
package.json:"build-storybook": "storybook build"npm run build-storybook to generate a storybook-static folder.storybook-static folder to Vercel as a separate project, or configure it as a subdirectory deployment.Chromatic is a cloud service built by the Storybook team. It provides visual regression testing, review workflows, and automatic publishing.
chromatic.com and connect your repository.npm install --save-dev chromaticnpx chromatic --project-token=YOUR_TOKEN