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:

  1. CI runs npx chromatic --project-token=… after build-storybook.
  2. Changed pixels flag the PR; reviewers accept intentional changes in the Chromatic UI.
  3. 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.

  1. Monorepo layoutpackages/ui/src/Button/Button.tsx colocated with Button.stories.tsx; Storybook config at repo root via Turborepo task storybook:dev and storybook:build.
  2. 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.
  3. Composite storiesProductCard stories use args for title, price, and stock state; a OutOfStock story sets inStock: false and asserts badge visibility via play function.
  4. MDX handbookIntroduction.mdx covers typography scale and spacing tokens; each primitive links to Figma via addon-designs.
  5. CI pipeline — on every PR: ESLint, Vitest unit tests, test-storybook play functions, Chromatic visual diff. Merge blocked on a11y violations or unreviewed Chromatic changes.
  6. Published buildbuild-storybook output deploys to design.harbor.internal; embed widget partners receive semver changelog links when @harbor/ui minor 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 catalogYou want minimal config and Vite-native speedTeam is engineers-only and UI surface is small
Chromatic or visual regression is part of CIReact-only monorepo; no MDX docs requirementComponents have few visual states to document
MDX handbooks and autodocs are first-class deliverablesBundle size and startup time are pain pointsInteraction coverage is fully caught by E2E tests
Multi-framework (React + Vue) in one orgAddon ecosystem is unnecessary overheadNo cross-functional review workflow exists
Partner or open-source consumers need published docsStories are dev-only, never deployedDesign 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.tsx with components; use consistent title prefixes.
  • Enable tags: ['autodocs'] and document non-obvious props via argTypes.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-a11y and fail CI on serious violations.
  • Write play functions for keyboard navigation and primary click paths.
  • Run build-storybook and 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 args driving 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