Storybook Sync
Keep Storybook stories in sync with components and deploy a living design system docs site.
Why
Storybook is the living documentation of your design system — it shows every component, every variant, every state, interactively. When stories drift from components (a prop added but no story covers it), that prop is invisible to designers and product managers who use Storybook to spec features. Deploying Storybook as a static site (Chromatic or self-hosted) gives non-engineers a URL to browse the design system without running the app. This is how design decisions get made against reality instead of Figma mockups that may not match the code. The sync is maintained by co-locating stories with components and failing CI when a component without a story is committed.
When
When you have ≥5 components. Takes 1-2 h to set up + deploy. Skip for apps with no reusable components.
How
1. Install Storybook with the React + Vite builder - npx storybook@latest init --builder vite - Co-locate stories: Component.stories.tsx next to Component.tsx 2. Write stories that cover every variant and state - Use args for props; use play() functions for interaction tests - Include edge cases: empty, loading, error, disabled, long text 3. Add a CI job that builds Storybook and deploys it - Build: npx storybook build -o storybook-static - Deploy to Chromatic (free for open-source) or Netlify/Vercel as a static site 4. Add a "no orphan components" check in CI - Script: find all .tsx in components/ without a matching .stories.tsx → fail - This prevents components from existing without documentation 5. Enable Storybook addons: a11y, controls, docs, viewport - a11y runs axe on every story; fails CI on violations - viewport tests responsive behavior without a separate test suite
Example
// Button.stories.tsx
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"] } } };
export default meta;
export const Default: StoryObj<typeof Button> = { args: { children: "Click me", variant: "primary" } };
export const Disabled: StoryObj<typeof Button> = { args: { children: "Disabled", disabled: true } };
export const LongText: StoryObj<typeof Button> = { args: { children: "A very long button label that might overflow" } };
// CI: .github/workflows/storybook.yml
- run: npx storybook build
- run: npx chromatic --project-token ${{ secrets.CHROMATIC_TOKEN }} --exit-zero-on-changesCommon Problems
- ⚠Stories that only cover the happy path, so the error and loading states are undocumented and untested.
- ⚠Not deploying Storybook, so designers can't browse components and keep asking devs 'what does this look like?'.
- ⚠Stories in a separate /stories directory that drift from components after a rename.
- ⚠No a11y addon, so accessibility violations ship to prod and are only caught by a user complaint.
Best Practices
- ✓Co-locate stories with components so a rename moves both files together.
- ✓Use interaction tests (play functions) to verify behavior, not just rendering — this catches logic bugs in the component.
- ✓Deploy Storybook on every PR (preview URL) so reviewers can see the component change before merging.
- ✓Add a 'design' addon that links each story to its Figma frame, so the source of truth is one click away.