Guide
Storybook fundamentals explained
A product team ships forty button variants across checkout, admin, and mobile — but designers review them by clicking through production pages, and engineers discover edge cases only after merge. Storybook breaks that loop: a dedicated workshop that renders each UI component in isolation, documents every state, and runs interaction tests without booting the full app. Paired with Vite or Webpack, Storybook became the default companion for React, Vue, and Svelte design systems. This guide covers Component Story Format (CSF), args and controls, decorators and context providers, play-function tests, autodocs, essential addons, Chromatic visual regression, a Harbor Commerce design system worked example, a tooling decision table, common pitfalls, and a production checklist.
What Storybook is and why teams adopt it
Storybook is an open-source environment for developing UI components outside
your application shell. Instead of navigating to /checkout?promo=SUMMER
to see one badge state, you open a sidebar entry and flip between
Default, Loading, Disabled, and Error stories
in seconds. The value compounds:
- Isolation — components render without routing, auth, or API mocks unless you explicitly add them via decorators.
- Living documentation — designers, PMs, and engineers share one URL (local or deployed) showing real rendered output, not Figma screenshots that drift.
- Regression safety — interaction tests and Chromatic snapshots catch visual and behavioral breaks before they reach users.
- Faster review — pull requests link to Storybook builds so reviewers comment on concrete UI states, not code diffs alone.
Storybook is not a replacement for end-to-end tests ( Playwright still validates full user journeys), nor is it a design tool like Figma — it renders your actual production components with real CSS ( Tailwind, CSS modules, styled-components). Think of it as the bridge between design intent and shipped pixels.
When Storybook earns its maintenance cost
- Shared design systems — buttons, inputs, tables, and modals reused across multiple apps or packages.
- Complex state matrices — data tables with sorting, empty, error, and skeleton states; forms with validation edge cases.
- Cross-functional review — non-engineers need a stable preview environment without local setup.
- Visual regression CI — Chromatic or similar services diff screenshots on every PR.
Skip Storybook for tiny apps with a dozen bespoke screens, one-off marketing landings, or teams where every component is used exactly once. The config overhead (main config, preview decorators, build pipeline) only pays off when components are reused and reviewed repeatedly.
Project setup and configuration
Modern projects scaffold Storybook with the CLI:
npx storybook@latest init
The init command detects your framework ( React + Vite, Next.js, Vue, SvelteKit) and generates:
.storybook/main.ts— framework preset, story globs, addon list, builder (Vite or Webpack)..storybook/preview.ts— global decorators, parameters, and default arg types shared by all stories.- Example stories under
src/stories/or colocated with components.
Run the dev workshop with npm run storybook (typically port 6006).
Production builds use npm run build-storybook, outputting static HTML
deployable to Chromatic, Netlify, or an S3 bucket behind your VPN.
Story discovery
Configure stories in main.ts to match your repo layout:
const config = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-essentials', '@storybook/addon-a11y'],
framework: '@storybook/react-vite',
}
export default config
Colocate Button.stories.tsx next to Button.tsx so
stories stay in sync when components move. Exclude test fixtures and generated
code from globs to keep the sidebar fast.
Component Story Format (CSF)
CSF 3 is the standard story definition: a default export describes the component meta; named exports are individual stories:
import type { Meta, StoryObj } from '@storybook/react'
import { Button } from './Button'
const meta = {
title: 'Commerce/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
variant: { control: 'select', options: ['primary', 'ghost', 'danger'] },
disabled: { control: 'boolean' },
},
} satisfies Meta<typeof Button>
export default meta
type Story = StoryObj<typeof meta>
export const Primary: Story = {
args: { children: 'Add to cart', variant: 'primary' },
}
export const Loading: Story = {
args: { children: 'Processing…', loading: true, variant: 'primary' },
}
The title field builds the sidebar hierarchy
(Commerce/Button). Use consistent prefixes (Forms/,
Layout/, Data/) so large design systems stay navigable.
Each story is a plain object — no special class syntax — which keeps
stories tree-shakeable and TypeScript-friendly.
Args: the single source of truth
args pass props to the component. Storybook merges story-level args
with meta-level defaults. The Controls panel edits args at runtime without code
changes, invaluable during design QA. Map complex props with argTypes:
control: 'object'for JSON-like props (with caution on large objects).control: 'color'for theme tokens.action: 'onClick'to log callback invocations in the Actions panel.table: { disable: true }to hide internal props from controls.
Decorators, providers, and global preview
Real components expect context: React Router, TanStack Query, theme providers, i18n. Decorators wrap stories with those dependencies:
// .storybook/preview.ts
import { withThemeProvider } from './decorators/withThemeProvider'
const preview = {
decorators: [withThemeProvider],
parameters: {
layout: 'centered',
backgrounds: { default: 'light' },
},
}
export default preview
Per-story decorators override or extend globals. A common pattern wraps only
modal stories with a fixed viewport and portal root. Keep decorators thin —
extract shared providers into .storybook/decorators/ modules
importable from both Storybook and
Vitest
component tests so test and workshop environments stay aligned.
Parameters
parameters configure addons without wrapping JSX: viewport presets
(mobile, tablet, desktop), docs page layout, Chromatic diff thresholds, or
mocking dates via @storybook/addon-mock-date. Set
parameters.chromatic.disableSnapshot = true on flaky animation
stories to avoid false positives in visual regression.
Autodocs and MDX documentation
Add tags: ['autodocs'] to meta (or enable globally) and Storybook
generates a Docs tab from TypeScript prop types and argTypes
descriptions. For narrative documentation — usage guidelines, do/don't
examples, accessibility notes — author .mdx files:
import { Meta, Canvas, Controls } from '@storybook/blocks'
import * as ButtonStories from './Button.stories'
<Meta of={ButtonStories} />
## Usage
Use primary buttons for the main call to action on a page. Limit to one per viewport.
<Canvas of={ButtonStories.Primary} />
<Controls of={ButtonStories.Primary} />
Autodocs reduces duplicate README maintenance; MDX pages become the canonical design-system handbook designers link from Notion or Zeroheight. Keep MDX focused on behavior and accessibility — pixel specs still live in Figma, but code examples must match what ships.
Interaction tests with play functions
Storybook 7+ integrates
Vitest
and Testing Library for in-browser interaction tests via play:
import { userEvent, within, expect } from '@storybook/test'
export const OpensMenu: Story = {
args: { label: 'Account' },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
await userEvent.click(canvas.getByRole('button', { name: 'Account' }))
await expect(canvas.getByRole('menu')).toBeVisible()
},
}
Run all play functions in CI with npx test-storybook (or the
Vitest addon). These tests are faster than full E2E because they skip login,
routing, and API bootstrapping — but they only cover component-level
behavior. Combine play functions for form validation and keyboard navigation
with Playwright suites for checkout and auth flows.
Essential addons
@storybook/addon-essentials bundles the most-used tools:
- Controls — live prop editing from
argTypes. - Actions — logs event handler calls (
onClick,onChange). - Viewport — responsive breakpoints without resizing the browser manually.
- Backgrounds — light/dark surface previews for contrast checks.
- Measure & outline — debug layout and box model during reviews.
Add @storybook/addon-a11y to run axe-core checks per story — catch missing labels, insufficient contrast, and invalid ARIA before merge. @storybook/addon-interactions provides the step debugger for play functions. For design-token workflows, @storybook/addon-designs embeds Figma frames alongside live components so reviewers compare spec to implementation side by side.
Chromatic and visual regression
Chromatic (Storybook’s commercial cloud, with a generous free tier) captures a screenshot of every story on each CI build and diffs against the approved baseline. Workflow:
- CI runs
npx chromatic --project-token=…afterbuild-storybook. - Changed pixels flag the PR; reviewers accept intentional changes in the Chromatic UI.
- Accepted baselines become the new truth for future diffs.
Visual regression excels at catching unintended CSS drift — a padding change
that breaks alignment across twelve card variants surfaces as one Chromatic
failure instead of twelve manual QA passes. Mitigate flake: disable animations
in preview (parameters.chromatic.pauseAnimationAtEnd), use fixed
dates for time-dependent components, and seed random avatars. Self-hosted
alternatives (Lost Pixel, Percy) follow the same pattern if policy forbids SaaS.
Worked example: Harbor Commerce design system
Harbor Commerce operates a B2B marketplace with separate buyer and seller
dashboards plus a marketing site. The team extracted a @harbor/ui
package (React + Tailwind) and adopted Storybook as the contract between design
and three consuming apps. Goals: document every component state, run a11y and
interaction tests in CI, and publish a password-protected Storybook URL for
vendor partners integrating Harbor embed widgets.
- Monorepo layout —
packages/ui/src/Button/Button.tsxcolocated withButton.stories.tsx; Storybook config at repo root via Turborepo taskstorybook:devandstorybook:build. - Global preview — decorators wrap all stories with Harbor theme provider (CSS variables), mock React Router (
MemoryRouter), and TanStack Query client with empty cache. Viewport presets match Harbor’s three breakpoints. - Composite stories —
ProductCardstories useargsfor title, price, and stock state; aOutOfStockstory setsinStock: falseand asserts badge visibility via play function. - MDX handbook —
Introduction.mdxcovers typography scale and spacing tokens; each primitive links to Figma via addon-designs. - CI pipeline — on every PR: ESLint, Vitest unit tests,
test-storybookplay functions, Chromatic visual diff. Merge blocked on a11y violations or unreviewed Chromatic changes. - Published build —
build-storybookoutput deploys todesign.harbor.internal; embed widget partners receive semver changelog links when@harbor/uiminor versions ship new props.
Six months in, support tickets about “button looks wrong in our iframe” dropped sharply — partners referenced specific story URLs in tickets, and engineers reproduced states without logging into production seller accounts.
Tooling decision table
| Choose Storybook when… | Prefer Ladle when… | Prefer Vitest + RTL only when… |
|---|---|---|
| Designers and PMs need a browsable component catalog | You want minimal config and Vite-native speed | Team is engineers-only and UI surface is small |
| Chromatic or visual regression is part of CI | React-only monorepo; no MDX docs requirement | Components have few visual states to document |
| MDX handbooks and autodocs are first-class deliverables | Bundle size and startup time are pain points | Interaction coverage is fully caught by E2E tests |
| Multi-framework (React + Vue) in one org | Addon ecosystem is unnecessary overhead | No cross-functional review workflow exists |
| Partner or open-source consumers need published docs | Stories are dev-only, never deployed | Design system maturity is premature |
Common pitfalls
- Stories diverge from production — decorators inject fake data shapes the real app never uses; sync fixtures with API types.
- God decorators — one preview wraps twelve providers and slows every story; scope heavy decorators to story files that need them.
- Missing empty and error states — only happy-path stories ship; edge cases still break in production.
- Flaky Chromatic baselines — animations, dates, and random media cause false diffs; stabilize or disable snapshots per story.
- Ignoring a11y addon failures — treating axe warnings as noise defeats the purpose; fix or explicitly document exceptions.
- Orphan stories after refactors — globs pick up deleted components; CI story builds catch this if enforced on every PR.
- Duplicating E2E in play functions — full checkout flows belong in Playwright; play functions should stay component-scoped.
- Skipping deployed Storybook — local-only workshops hide regressions from reviewers who never run
npm install.
Practitioner checklist
- Colocate
*.stories.tsxwith components; use consistenttitleprefixes. - Enable
tags: ['autodocs']and document non-obvious props viaargTypes.description. - Extract shared decorators (theme, router, query client) into reusable modules.
- Cover loading, empty, error, and disabled states for every public component.
- Add
@storybook/addon-a11yand fail CI on serious violations. - Write play functions for keyboard navigation and primary click paths.
- Run
build-storybookand Chromatic (or equivalent) on every pull request. - Publish static Storybook to a stable URL for design and partner review.
- Align story args with TypeScript prop types so autodocs stay accurate.
- Review Chromatic diffs in the same PR that changes CSS — never merge UI changes with pending visual approvals.
Key takeaways
- Storybook is the UI workshop for developing, documenting, and testing components in isolation.
- CSF stories use meta + named exports with
argsdriving both render and Controls panel. - Decorators supply routers, themes, and data providers without booting the full application.
- Play functions run fast interaction tests; Chromatic catches visual regressions across all stories.
- Deployed Storybook turns the design system into a shared, reviewable contract for the whole organization.
Related reading
- React fundamentals explained — components, hooks, and patterns Storybook stories render
- Vite fundamentals explained — dev server and build tool behind
@storybook/react-vite - Vitest fundamentals explained — unit and component tests paired with Storybook play functions
- Tailwind CSS fundamentals explained — utility styling common in design-system packages