Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Storybook User Guide

Generated May 2026 from storybookjs/storybook at 245043f

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

Storybook is the industry-standard frontend workshop for building, testing, and documenting UI components in isolation. Whether you're working with React, Vue, Angular, Svelte, or plain HTML, Storybook gives you a dedicated browser-based environment where you develop components outside your app, write visual test cases called "stories," and generate living documentation — all from a single tool.

This guide covers everything you need to go from zero to productive with Storybook 8.x, including installation for every supported framework, configuration, story authoring, testing, theming, and dozens of real-world recipes.


1. Quick Start

What Is Storybook?

Storybook is a frontend development tool that renders your UI components in a sandboxed environment — completely separate from your application's business logic, data layer, and routing. You describe component states as stories (plain JS/TS exports), and Storybook provides a browser UI to browse, interact with, and test each one. It supports 22 framework/builder combinations, ships with a rich addon ecosystem, and produces static builds you can deploy anywhere.

Key Features

  • Component isolation — develop and debug components without running your full app
  • Component Story Format (CSF) — stories are portable JavaScript modules, not proprietary config
  • Auto-generated documentation — DocsPage creates API docs from your component props and stories
  • Interaction testing — write play functions that simulate user behavior and assert outcomes
  • Accessibility auditing — built-in axe-core integration catches a11y violations in every story
  • Visual testing — snapshot and visual regression testing via Chromatic integration
  • Theme switching — preview components across multiple themes and color modes
  • Static deployment — build a zero-dependency site you can host on any CDN

Prerequisites

Requirement Minimum Version
Node.js 20.19+ or 22.12+
Package manager npm 7+, Yarn 1.x/3+/4+, or pnpm 7+
Existing project A frontend project with a framework already configured
Browser Chrome 131+, Firefox 136+, Safari 18.3+, or Edge 134+

Important

Storybook requires an existing project with a framework already set up. It is not a standalone tool — it layers onto your React, Vue, Angular, Svelte, or other frontend project. If you don't have a project yet, use npx storybook sandbox to create one.

Your First Storybook in 60 Seconds

# From your existing project root:
npx storybook@latest init

This detects your framework, installs the right packages, creates a .storybook/ config directory, and generates example stories. When it finishes:

npx storybook dev

Expected output:

╭──────────────────────────────────────────────────╮
│                                                  │
│   Storybook 8.x.x for react-vite started         │
│   x.xx seconds for manager and y.yy for preview  │
│                                                  │
│   Local:    http://localhost:6006/                │
│                                                  │
╰──────────────────────────────────────────────────╯

Open http://localhost:6006 in your browser. You'll see the Storybook UI with a sidebar listing example stories.

Minimal Story Example

Create src/components/Button.stories.ts (or .tsx, .js, .jsx):

import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
};

export default meta;
type Story = StoryObj<typeof Button>;

export const Primary: Story = {
  args: {
    label: 'Click me',
    primary: true,
  },
};

export const Secondary: Story = {
  args: {
    label: 'Click me',
    primary: false,
  },
};

Save the file. Storybook's dev server hot-reloads, and you'll see Components > Button in the sidebar with Primary and Secondary variants.

What to Try Next

  • Section 3 — Configure Storybook's behavior, addons, and UI
  • Section 4.1 — Deep dive on writing stories in CSF
  • Section 4.3 — Add interaction tests to your stories
  • Section 5 — Real-world recipes: Next.js setup, a11y testing, static deployment, and more

2. Installation

2.1 System Requirements

Component Requirement Notes
Node.js 20.19+ or 22.12+ Enforced at startup; older versions produce a clear error
npm 7+ Ships with Node.js 20+
Yarn 1.22+ / 3+ / 4+ All major Yarn versions supported
pnpm 7+ Supported with shamefully-hoist=true recommended
Git Any recent version Required for storybook init framework detection
OS macOS, Linux, Windows, WSL2 All major platforms
Disk space ~200-500 MB Varies by framework and addon count
Browser Chrome 131+, Firefox 136+, Safari 18.3+, Edge 134+ For the Storybook UI

The init command auto-detects your framework and installs the correct packages:

npx storybook@latest init

This command:

  1. Detects your project's framework (React, Vue, Angular, etc.) and builder (Vite or Webpack)
  2. Installs the matching @storybook/* packages
  3. Creates .storybook/main.js and .storybook/preview.js
  4. Generates example stories in your source directory
  5. Adds storybook and build-storybook scripts to package.json

With specific package managers

# npm (default)
npx storybook@latest init

# Yarn
yarn dlx storybook@latest init

# pnpm
pnpm dlx storybook@latest init

2.3 Framework-Specific Installation

If auto-detection doesn't match your setup, or you want explicit control, install the specific framework package manually.

React + Vite

npm install --save-dev @storybook/react-vite storybook

.storybook/main.ts:

import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
  framework: '@storybook/react-vite',
  addons: ['@storybook/addon-docs'],
};

export default config;

React + Webpack 5

npm install --save-dev @storybook/react-webpack5 storybook

.storybook/main.ts:

import type { StorybookConfig } from '@storybook/react-webpack5';

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
  framework: '@storybook/react-webpack5',
  addons: ['@storybook/addon-docs'],
};

export default config;

Next.js (Webpack)

npm install --save-dev @storybook/nextjs storybook

.storybook/main.ts:

import type { StorybookConfig } from '@storybook/nextjs';

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)', '../app/**/*.stories.@(js|jsx|ts|tsx)'],
  framework: '@storybook/nextjs',
  addons: ['@storybook/addon-docs'],
};

export default config;

Next.js + Vite

npm install --save-dev @storybook/nextjs-vite storybook

.storybook/main.ts:

import type { StorybookConfig } from '@storybook/nextjs-vite';

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)', '../app/**/*.stories.@(js|jsx|ts|tsx)'],
  framework: '@storybook/nextjs-vite',
  addons: ['@storybook/addon-docs'],
};

export default config;

Vue 3 + Vite

npm install --save-dev @storybook/vue3-vite storybook

.storybook/main.ts:

import type { StorybookConfig } from '@storybook/vue3-vite';

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
  framework: '@storybook/vue3-vite',
  addons: ['@storybook/addon-docs'],
};

export default config;

Angular

npm install --save-dev @storybook/angular storybook

.storybook/main.ts:

import type { StorybookConfig } from '@storybook/angular';

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
  framework: '@storybook/angular',
  addons: ['@storybook/addon-docs'],
};

export default config;

Note

Angular uses its own builder internally. You do not need to install a separate @storybook/builder-* package.

Svelte + Vite

npm install --save-dev @storybook/svelte-vite storybook

.storybook/main.ts:

import type { StorybookConfig } from '@storybook/svelte-vite';

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|svelte)'],
  framework: '@storybook/svelte-vite',
  addons: ['@storybook/addon-docs'],
};

export default config;

SvelteKit

npm install --save-dev @storybook/sveltekit storybook

.storybook/main.ts:

import type { StorybookConfig } from '@storybook/sveltekit';

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|svelte)'],
  framework: '@storybook/sveltekit',
  addons: ['@storybook/addon-docs'],
};

export default config;

HTML + Vite

npm install --save-dev @storybook/html-vite storybook

.storybook/main.ts:

import type { StorybookConfig } from '@storybook/html-vite';

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
  framework: '@storybook/html-vite',
  addons: ['@storybook/addon-docs'],
};

export default config;

Web Components + Vite

npm install --save-dev @storybook/web-components-vite storybook

.storybook/main.ts:

import type { StorybookConfig } from '@storybook/web-components-vite';

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
  framework: '@storybook/web-components-vite',
  addons: ['@storybook/addon-docs'],
};

export default config;

Preact + Vite

npm install --save-dev @storybook/preact-vite storybook

Ember

npm install --save-dev @storybook/ember storybook

React Native Web + Vite

npm install --save-dev @storybook/react-native-web-vite storybook

Server + Webpack 5

npm install --save-dev @storybook/server-webpack5 storybook

2.4 Post-Install Verification

After installation, verify everything works:

# Start the dev server
npx storybook dev

# Verify it builds successfully
npx storybook build

Check these files exist:

.storybook/
  main.js (or main.ts)
  preview.js (or preview.ts)
package.json  # should contain "storybook" and "build-storybook" scripts

Verify package.json scripts:

{
  "scripts": {
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build"
  }
}

Run the diagnostics command if anything looks wrong:

npx storybook doctor

Expected output when healthy:

Storybook doctor
================

Checking versions..
  ✓ All Storybook packages are at the same version

Checking for configuration issues..
  ✓ No issues found

Checking for dependency conflicts..
  ✓ No conflicts detected

2.5 Installation Troubleshooting

"Node version X is not supported"

Cause: Storybook 8.x requires Node.js 20.19+ or 22.12+. Older versions (18.x, 20.0-20.18, etc.) are rejected at startup.

Solution:

# Check your version
node --version

# Use nvm to switch
nvm install 22
nvm use 22

"Could not detect framework"

Cause: storybook init couldn't find a recognized framework in your package.json or project files.

Solution: Install your framework first, then re-run init:

npm install react react-dom
npx storybook@latest init

Or specify the framework type explicitly during init:

npx storybook@latest init --type react_vite

Peer dependency conflicts with npm

Cause: npm 7+ enforces strict peer dependencies by default, and Storybook's addon ecosystem can have version mismatches.

npm install --save-dev @storybook/react-vite storybook --legacy-peer-deps

"Cannot find module '@storybook/...'"

Cause: Incomplete installation or version mismatch between Storybook packages.

Solution:

# Remove node_modules and reinstall
rm -rf node_modules package-lock.json
npm install
npx storybook@latest upgrade --fix

Port 6006 already in use

# Use a different port
npx storybook dev -p 6007

# Or find and kill the process using 6006
lsof -ti:6006 | xargs kill -9   # macOS/Linux
netstat -ano | findstr 6006      # Windows

Storybook starts but shows a blank page

Cause: Usually a story glob mismatch — Storybook can't find any stories.

Solution: Check the stories array in .storybook/main.js:

// Verify this glob matches where your stories actually live
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],

Test the glob:

# Should return your story files
find src -name "*.stories.*"

"Addon X is not compatible with Storybook Y"

Cause: Third-party addons may not support your Storybook version yet.

Solution:

# Check which addons are outdated
npx storybook doctor

# Upgrade all Storybook packages together
npx storybook@latest upgrade

Build fails with memory errors

Cause: Large projects can exhaust Node.js default memory (1.7 GB).

# Increase Node memory for builds
NODE_OPTIONS="--max-old-space-size=4096" npx storybook build

TypeScript errors during build

Cause: Storybook's TypeScript handling is separate from your app's. Strict TS settings can cause issues with story files.

Solution: Add a tsconfig path in your Storybook config:

// .storybook/main.ts
const config: StorybookConfig = {
  typescript: {
    check: false,  // Disable TS checking in Storybook (use your IDE/CI instead)
  },
};

"ERR_PACKAGE_PATH_NOT_EXPORTED" on startup

Cause: Package export map conflicts, typically with ESM/CJS interop.

Solution:

# Clear caches
rm -rf node_modules/.cache
npx storybook dev --no-manager-cache

pnpm: "Missing peer dependencies"

Cause: pnpm's strict dependency resolution doesn't hoist peers by default.

Solution: Add to .npmrc:

shamefully-hoist=true
auto-install-peers=true

Then reinstall:

pnpm install

3. Configuration

Storybook configuration lives in three files inside the .storybook/ directory. Each controls a different aspect of the system.

File Controls When It Runs
main.js|ts Project-level: stories location, addons, framework, build settings At build/dev start
preview.js|ts Story rendering: decorators, parameters, global types In the preview iframe
manager.js|ts Manager UI: sidebar, theme, toolbar In the manager frame

3.1 Main Configuration (.storybook/main.js|ts)

This is the primary configuration file. It tells Storybook where to find stories, which addons to load, and how to build.

Complete annotated example

import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
  // ─── Story Discovery ──────────────────────────────────────────
  // Glob patterns for finding story files. Relative to this config file.
  stories: [
    '../src/**/*.mdx',                           // MDX documentation files
    '../src/**/*.stories.@(js|jsx|ts|tsx)',       // CSF story files
  ],

  // ─── Addons ────────────────────────────────────────────────────
  // Addons extend Storybook with panels, decorators, and tools.
  // Order matters: first addon's panel appears first in the UI.
  addons: [
    '@storybook/addon-docs',
    '@storybook/addon-a11y',
    '@storybook/addon-links',
    '@storybook/addon-themes',
    '@storybook/addon-onboarding',
    // Addons with options use object syntax:
    {
      name: '@storybook/addon-vitest',
      options: {
        // addon-specific options
      },
    },
  ],

  // ─── Framework ─────────────────────────────────────────────────
  // Must match your project's framework + builder combination.
  framework: {
    name: '@storybook/react-vite',
    options: {
      // Framework-specific options
    },
  },

  // ─── Static Files ──────────────────────────────────────────────
  // Directories to serve as static assets (images, fonts, etc.)
  staticDirs: ['../public'],

  // ─── TypeScript ────────────────────────────────────────────────
  typescript: {
    check: false,                // Don't type-check via fork-ts-checker
    reactDocgen: 'react-docgen', // Auto-extract prop types for docs
  },

  // ─── Feature Flags ─────────────────────────────────────────────
  features: {
    // Enable experimental features here
  },

  // ─── Build Configuration ───────────────────────────────────────
  // Override the builder's config (Vite or Webpack)
  async viteFinal(config) {
    // Modify the Vite config
    return config;
  },

  // For Webpack-based frameworks:
  // async webpackFinal(config) {
  //   return config;
  // },

  // ─── Output ────────────────────────────────────────────────────
  // Customize the build output directory (default: 'storybook-static')
  // Used only by `storybook build`
};

export default config;

Key configuration options

Option Type Default Description
stories string[] ['../src/**/*.stories.@(js|jsx|ts|tsx)'] Glob patterns to find story files
addons (string | object)[] [] Addons to load, in order
framework string | object (required) Framework package name
staticDirs string[] [] Directories to serve as static files
typescript object { check: false } TypeScript compilation settings
features object {} Feature flags for experimental features
core object {} Core Storybook settings

Story glob patterns

The stories array supports standard glob syntax:

stories: [
  // All stories in src/
  '../src/**/*.stories.@(js|jsx|ts|tsx)',

  // Only specific directories
  '../src/components/**/*.stories.tsx',
  '../src/pages/**/*.stories.tsx',

  // Include MDX docs alongside stories
  '../src/**/*.mdx',

  // Exclude certain patterns (prefix with !)
  '!../**/internal/**',
]

Tip

Use specific globs instead of broad **/* patterns to speed up Storybook's startup. On large projects, narrowing the glob from ../src/**/*.stories.* to specific directories can cut startup time significantly.

3.2 Preview Configuration (.storybook/preview.js|ts)

The preview file controls how stories render. Decorators, default parameters, and global types all go here.

import type { Preview } from '@storybook/react';

const preview: Preview = {
  // ─── Parameters ────────────────────────────────────────────────
  // Default parameters applied to every story.
  parameters: {
    // Control the "controls" addon behavior
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },

    // Layout: 'centered', 'fullscreen', or 'padded' (default)
    layout: 'centered',

    // Docs page configuration
    docs: {
      toc: true, // Show table of contents on docs pages
    },

    // Backgrounds addon
    backgrounds: {
      default: 'light',
      values: [
        { name: 'light', value: '#ffffff' },
        { name: 'dark', value: '#333333' },
      ],
    },
  },

  // ─── Decorators ────────────────────────────────────────────────
  // Wrappers applied to every story (e.g., providers, layouts).
  // Applied bottom-up: last in array wraps closest to the component.
  decorators: [
    (Story) => (
      <div style={{ margin: '3em' }}>
        <Story />
      </div>
    ),
  ],

  // ─── Global Types ──────────────────────────────────────────────
  // Define toolbar items that set global values.
  globalTypes: {
    locale: {
      description: 'Internationalization locale',
      toolbar: {
        icon: 'globe',
        items: ['en', 'fr', 'es', 'de', 'ja'],
        title: 'Locale',
        dynamicTitle: true,
      },
    },
  },

  // ─── Initial Globals ──────────────────────────────────────────
  initialGlobals: {
    locale: 'en',
  },

  // ─── Tags ──────────────────────────────────────────────────────
  // Default tags applied to every story.
  tags: ['autodocs'],
};

export default preview;

Common parameter combinations

Centered layout with dark background:

parameters: {
  layout: 'centered',
  backgrounds: { default: 'dark' },
}

Fullscreen layout (for page-level stories):

parameters: {
  layout: 'fullscreen',
}

Disable specific addon panels per-story:

parameters: {
  controls: { disable: true },
  actions: { disable: true },
}

3.3 Manager Configuration (.storybook/manager.js|ts)

Customize the Storybook UI shell (sidebar, header, toolbar).

import { addons } from 'storybook/manager-api';
import { create } from 'storybook/theming';

addons.setConfig({
  // ─── Theme ─────────────────────────────────────────────────────
  theme: create({
    base: 'light',                    // 'light' or 'dark'
    brandTitle: 'My Design System',
    brandUrl: 'https://example.com',
    brandImage: '/logo.svg',          // Served from staticDirs
    brandTarget: '_self',
  }),

  // ─── Sidebar ───────────────────────────────────────────────────
  sidebar: {
    showRoots: true,                  // Show top-level groups
    collapsedRoots: ['archive'],      // Auto-collapse these groups
  },

  // ─── Panel Position ────────────────────────────────────────────
  panelPosition: 'bottom',           // 'bottom' or 'right'
  showPanel: true,
  enableShortcuts: true,

  // ─── Initial View ──────────────────────────────────────────────
  initialActive: 'sidebar',          // 'sidebar', 'canvas', or 'addons'
});

3.4 Environment Variables

Storybook recognizes several environment variables:

Variable Purpose Where to Set
STORYBOOK Set to 'true' automatically when Storybook is running Auto-set
STORYBOOK_PROJECT_ROOT Override automatic project root detection Shell / .env
NODE_ENV Controls dev/production mode Shell

Custom environment variables prefixed with STORYBOOK_ are automatically exposed to your stories:

# In your shell or .env
STORYBOOK_API_URL=https://api.example.com
// In your stories
console.log(process.env.STORYBOOK_API_URL); // "https://api.example.com"

Warning

Only variables prefixed with STORYBOOK_ are exposed to the browser bundle. Never put secrets in STORYBOOK_* variables — they will be visible in the built output.

3.5 Builder Configuration

Vite customization

// .storybook/main.ts
export default {
  framework: '@storybook/react-vite',
  async viteFinal(config) {
    // Add custom Vite plugins
    const { mergeConfig } = await import('vite');
    return mergeConfig(config, {
      resolve: {
        alias: {
          '@': '/src',
        },
      },
      // Add more Vite config here
    });
  },
};

Webpack customization

// .storybook/main.ts
export default {
  framework: '@storybook/react-webpack5',
  webpackFinal: async (config) => {
    config.resolve = {
      ...config.resolve,
      alias: {
        ...config.resolve?.alias,
        '@': path.resolve(__dirname, '../src'),
      },
    };
    return config;
  },
};

Note

For the full configuration reference including all builder options, performance tuning, and advanced features, see the Admin Guide.


4. Core Usage

4.1 Writing Stories (Component Story Format)

Stories are the fundamental unit of Storybook. Each story represents a single state of a component, written in Component Story Format (CSF) — plain JavaScript/TypeScript modules with a default export for metadata and named exports for individual stories.

CSF anatomy

Every story file has two parts:

  1. Default export — metadata about the component (the Meta object)
  2. Named exports — individual stories (each a StoryObj)
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

// 1. Meta: describes the component
const meta: Meta<typeof Button> = {
  title: 'Components/Button',    // Where it appears in the sidebar
  component: Button,             // The component itself
  tags: ['autodocs'],            // Generate a docs page automatically
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary', 'danger'],
      description: 'Visual style variant',
    },
    size: {
      control: 'radio',
      options: ['sm', 'md', 'lg'],
    },
    onClick: { action: 'clicked' }, // Log clicks in the Actions panel
  },
  args: {
    // Default args shared by all stories in this file
    label: 'Button',
    size: 'md',
  },
};

export default meta;
type Story = StoryObj<typeof meta>;

// 2. Named exports: individual stories
export const Primary: Story = {
  args: {
    variant: 'primary',
    label: 'Primary Button',
  },
};

export const Secondary: Story = {
  args: {
    variant: 'secondary',
    label: 'Secondary Button',
  },
};

export const Danger: Story = {
  args: {
    variant: 'danger',
    label: 'Delete Account',
  },
};

export const Small: Story = {
  args: {
    size: 'sm',
    label: 'Small',
  },
};

export const Large: Story = {
  args: {
    size: 'lg',
    label: 'Large Button',
  },
};

The title field controls where stories appear in the sidebar. Use / to create groups:

// These create a nested hierarchy:
title: 'Components/Button'         // Components > Button
title: 'Components/Forms/Input'    // Components > Forms > Input
title: 'Pages/Dashboard'           // Pages > Dashboard
title: 'Design Tokens/Colors'      // Design Tokens > Colors

If you omit title, Storybook generates one from the file path.

Args and controls

Args are the dynamic inputs to your component. Storybook auto-generates UI controls for them based on the component's prop types.

const meta: Meta<typeof Card> = {
  component: Card,
  argTypes: {
    // Override auto-detected controls
    backgroundColor: { control: 'color' },
    padding: {
      control: { type: 'range', min: 0, max: 100, step: 4 },
    },
    status: {
      control: 'inline-radio',
      options: ['active', 'inactive', 'pending'],
    },
    createdAt: { control: 'date' },
    content: { control: 'text' },
    showHeader: { control: 'boolean' },
    items: { control: 'object' },
  },
};

Available control types:

Control Data Type UI Element
boolean boolean Toggle switch
text string Text input
number number Number input
range number Slider
color string Color picker
date Date Date picker
select enum Dropdown
radio enum Radio buttons
inline-radio enum Inline radio buttons
check enum[] Checkboxes
inline-check enum[] Inline checkboxes
object object JSON editor
file File File upload

Decorators

Decorators wrap stories with additional markup, providers, or context. Apply them at the story, component, or global level.

Story-level decorator:

export const WithThemeProvider: Story = {
  decorators: [
    (Story) => (
      <ThemeProvider theme="dark">
        <Story />
      </ThemeProvider>
    ),
  ],
  args: {
    label: 'Themed Button',
  },
};

Component-level decorator (in meta):

const meta: Meta<typeof Sidebar> = {
  component: Sidebar,
  decorators: [
    (Story) => (
      <div style={{ width: '280px', height: '100vh' }}>
        <Story />
      </div>
    ),
  ],
};

Global decorator (in preview.js):

// .storybook/preview.ts
const preview: Preview = {
  decorators: [
    (Story) => (
      <I18nProvider locale="en">
        <Story />
      </I18nProvider>
    ),
  ],
};

Decorator execution order: global decorators run first (outermost), then component-level, then story-level (innermost, closest to the component).

Render functions

For complete control over rendering, use the render function:

export const CustomRender: Story = {
  args: {
    label: 'Submit',
  },
  render: (args) => (
    <form onSubmit={(e) => e.preventDefault()}>
      <Button {...args} />
    </form>
  ),
};

Multiple components in one story

export const ButtonGroup: Story = {
  render: () => (
    <div style={{ display: 'flex', gap: '8px' }}>
      <Button variant="primary" label="Save" />
      <Button variant="secondary" label="Cancel" />
      <Button variant="danger" label="Delete" />
    </div>
  ),
};

Parameters

Parameters are static metadata that control addon behavior:

export const OnDarkBackground: Story = {
  parameters: {
    backgrounds: { default: 'dark' },
    layout: 'centered',
    docs: {
      description: {
        story: 'This story demonstrates the button on a dark background.',
      },
    },
  },
  args: {
    variant: 'primary',
    label: 'On Dark',
  },
};

Naming conventions

Convention Example Use For
PascalCase named exports export const Primary: Story All stories
Descriptive names export const DisabledWithTooltip: Story Complex states
Category prefixes in title title: 'Forms/Input' Sidebar organization
*.stories.tsx extension Button.stories.tsx Story file naming

4.2 Writing Documentation

Storybook provides two documentation systems: auto-generated DocsPage and custom MDX pages.

Auto-generated docs (DocsPage)

Add the autodocs tag to automatically generate a documentation page from your stories:

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  tags: ['autodocs'],  // ← This generates a Docs page
};

The auto-generated page includes:

  • Component description (from JSDoc comments on the component)
  • A primary story canvas
  • Controls table (all props with types, defaults, and descriptions)
  • All stories rendered with their source code

Adding descriptions via JSDoc:

/**
 * Primary UI button component for user interactions.
 *
 * Supports three variants: primary, secondary, and danger.
 * All buttons follow WCAG 2.1 AA contrast requirements.
 */
export const Button = ({ label, variant = 'primary', ...props }) => {
  // ...
};

The JSDoc comment appears as the component description on the docs page.

Custom docs with MDX

MDX lets you mix Markdown with JSX components. Create .mdx files alongside or instead of story files:

{/* src/components/Button.mdx */}
import { Meta, Story, Canvas, Controls, Source } from '@storybook/blocks';
import * as ButtonStories from './Button.stories';

<Meta of={ButtonStories} />

# Button

The Button component is the primary interactive element in our design system.

## Usage Guidelines

Use buttons for actions, not navigation. For navigation, use links.

## Variants

### Primary

Use the primary variant for the main call-to-action on any given page.

<Canvas of={ButtonStories.Primary} />

### Secondary

Use secondary for less prominent actions that sit alongside a primary button.

<Canvas of={ButtonStories.Secondary} />

## Props

<Controls />

## Do's and Don'ts

| Do | Don't |
|---|---|
| Use sentence case for labels | Use ALL CAPS |
| Keep labels to 1-3 words | Write full sentences |
| Use primary for the main CTA | Use multiple primaries on one page |

Doc blocks

Storybook provides several MDX components (called "doc blocks") for embedding stories and controls into documentation:

Block Purpose Usage
<Meta> Attach the MDX page to a component's stories <Meta of={Stories} />
<Canvas> Render a story with a code snippet toggle <Canvas of={Stories.Primary} />
<Controls> Render the args/controls table <Controls />
<Story> Render a story inline (no chrome) <Story of={Stories.Primary} />
<Source> Show source code <Source of={Stories.Primary} />
<ArgTypes> Show prop types table <ArgTypes of={Button} />
<Description> Show component description <Description of={Button} />
<Primary> Render the primary (first) story <Primary />
<Stories> Render all stories <Stories />
<Title> Show the component title <Title />
<Subtitle> Show a subtitle <Subtitle />

Standalone documentation pages

Create MDX files that aren't tied to any component — useful for design system introductions, guidelines, and principles:

{/* src/docs/Introduction.mdx */}
import { Meta } from '@storybook/blocks';

<Meta title="Introduction" />

# Welcome to Our Design System

This Storybook documents every component in our design system...

Configure the stories glob to include MDX:

// .storybook/main.ts
stories: [
  '../src/**/*.mdx',
  '../src/**/*.stories.@(js|jsx|ts|tsx)',
],

4.3 Testing

Storybook integrates testing at multiple levels: interaction tests within stories, component tests via Vitest, and accessibility audits via axe-core.

Play functions (interaction testing)

Play functions run after a story renders, simulating user interactions and making assertions:

import { expect, fn, userEvent, within } from 'storybook/test';

const meta: Meta<typeof LoginForm> = {
  component: LoginForm,
  args: {
    onSubmit: fn(),  // Mock function that tracks calls
  },
};

export default meta;
type Story = StoryObj<typeof meta>;

export const FilledForm: Story = {
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);

    // Type into the email field
    const emailInput = canvas.getByLabelText('Email');
    await userEvent.clear(emailInput);
    await userEvent.type(emailInput, '[email removed]');

    // Type into the password field
    const passwordInput = canvas.getByLabelText('Password');
    await userEvent.clear(passwordInput);
    await userEvent.type(passwordInput, 'secretpassword');

    // Click submit
    const submitButton = canvas.getByRole('button', { name: /sign in/i });
    await userEvent.click(submitButton);

    // Assert the onSubmit handler was called with correct values
    await expect(args.onSubmit).toHaveBeenCalledWith({
      email: '[email removed]',
      password: 'secretpassword',
    });
  },
};

Play functions use Testing Library queries (getByRole, getByLabelText, getByText, etc.) and userEvent for realistic user interaction simulation.

Step-by-step play functions

Break complex interactions into labeled steps for better debugging:

import { expect, userEvent, within } from 'storybook/test';

export const CheckoutFlow: Story = {
  play: async ({ canvasElement, step }) => {
    const canvas = within(canvasElement);

    await step('Fill shipping address', async () => {
      await userEvent.type(canvas.getByLabelText('Street'), '123 Main St');
      await userEvent.type(canvas.getByLabelText('City'), 'Springfield');
      await userEvent.selectOptions(canvas.getByLabelText('State'), 'IL');
      await userEvent.type(canvas.getByLabelText('Zip'), '62704');
    });

    await step('Select shipping method', async () => {
      await userEvent.click(canvas.getByLabelText('Express (2-day)'));
    });

    await step('Submit order', async () => {
      await userEvent.click(canvas.getByRole('button', { name: /place order/i }));
      await expect(canvas.getByText('Order confirmed')).toBeInTheDocument();
    });
  },
};

Each step appears as a collapsible group in the Storybook Interactions panel.

Vitest addon (component testing)

The Vitest addon lets you run your stories as component tests in Vitest, giving you headless test execution and CI integration.

Requirements:

  • Vitest 3.0.0+
  • A Vite-based Storybook framework (react-vite, vue3-vite, svelte-vite, etc.)
  • MSW 2.0.0+ if you use Mock Service Worker

Setup:

npx storybook add @storybook/addon-vitest

This generates a Vitest workspace config. Add to your vitest.workspace.ts:

import { defineWorkspace } from 'vitest/config';
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';

export default defineWorkspace([
  // Your existing Vitest config
  'vitest.config.ts',
  // Storybook test workspace
  {
    extends: 'vite.config.ts',
    plugins: [storybookTest()],
    test: {
      name: 'storybook',
      browser: {
        enabled: true,
        headless: true,
        provider: 'playwright',
        instances: [{ browser: 'chromium' }],
      },
      setupFiles: ['.storybook/vitest.setup.ts'],
    },
  },
]);

Run your stories as tests:

# Run all story tests
npx vitest --project storybook

# Run in watch mode
npx vitest --project storybook --watch

Expected output:

✓ src/components/Button.stories.tsx (3 tests) 245ms
  ✓ Components/Button > Primary
  ✓ Components/Button > Secondary
  ✓ Components/Button > Danger
✓ src/components/LoginForm.stories.tsx (2 tests) 512ms
  ✓ Components/LoginForm > Empty
  ✓ Components/LoginForm > FilledForm

Test Files  2 passed (2)
     Tests  5 passed (5)

Tip

The Vitest addon runs every story as a test — including its play function. This means your interaction tests automatically become CI-ready without any extra configuration.

Accessibility testing

The @storybook/addon-a11y addon runs axe-core against every story, catching WCAG violations.

npx storybook add @storybook/addon-a11y

Once installed, every story gets an Accessibility panel in the addon bar showing violations, passes, and incomplete checks.

Configure a11y rules per-story or globally:

// Global a11y config in preview.ts
const preview: Preview = {
  parameters: {
    a11y: {
      // axe-core configuration
      config: {
        rules: [
          { id: 'color-contrast', enabled: true },
          { id: 'landmark-one-main', enabled: false }, // Disable for component-level stories
        ],
      },
      // Element to scan (defaults to '#storybook-root')
      element: '#storybook-root',
    },
  },
};

Per-story override:

export const LowContrast: Story = {
  parameters: {
    a11y: {
      config: {
        rules: [
          { id: 'color-contrast', enabled: false }, // Known issue, tracked in JIRA-123
        ],
      },
    },
  },
};

Disable a11y for a specific story:

export const Decorative: Story = {
  parameters: {
    a11y: { disable: true },
  },
};

4.4 Theming

Storybook UI theming

Customize Storybook's own UI (sidebar, toolbar, panel) via the manager:

// .storybook/manager.ts
import { addons } from 'storybook/manager-api';
import { create } from 'storybook/theming';

const theme = create({
  base: 'dark',

  // Brand
  brandTitle: 'Acme Design System',
  brandUrl: 'https://acme.com',
  brandImage: '/acme-logo.svg',

  // Colors
  colorPrimary: '#FF4785',
  colorSecondary: '#1EA7FD',

  // UI
  appBg: '#1B1C1D',
  appContentBg: '#282C34',
  appPreviewBg: '#ffffff',
  appBorderColor: '#3B3C3D',
  appBorderRadius: 6,

  // Typography
  fontBase: '"Inter", sans-serif',
  fontCode: '"Fira Code", monospace',

  // Text colors
  textColor: '#FFFFFF',
  textInverseColor: '#1B1C1D',
  textMutedColor: '#999999',

  // Toolbar
  barTextColor: '#999999',
  barSelectedColor: '#1EA7FD',
  barHoverColor: '#1EA7FD',
  barBg: '#1B1C1D',

  // Inputs
  inputBg: '#3B3C3D',
  inputBorder: '#5C5F66',
  inputTextColor: '#FFFFFF',
  inputBorderRadius: 4,
});

addons.setConfig({ theme });

Component theming with addon-themes

The @storybook/addon-themes addon adds a toolbar dropdown for switching between your application's themes:

npx storybook add @storybook/addon-themes

CSS class-based theming:

// .storybook/preview.ts
import { withThemeByClassName } from '@storybook/addon-themes';

const preview: Preview = {
  decorators: [
    withThemeByClassName({
      themes: {
        light: 'theme-light',
        dark: 'theme-dark',
        high_contrast: 'theme-high-contrast',
      },
      defaultTheme: 'light',
    }),
  ],
};

Data attribute theming:

import { withThemeByDataAttribute } from '@storybook/addon-themes';

const preview: Preview = {
  decorators: [
    withThemeByDataAttribute({
      themes: {
        light: 'light',
        dark: 'dark',
      },
      defaultTheme: 'light',
      attributeName: 'data-theme',
    }),
  ],
};

The @storybook/addon-links addon enables navigation between stories — useful for building multi-step workflows or linking related components.

npx storybook add @storybook/addon-links

Link from one story to another:

import { linkTo } from '@storybook/addon-links';

export const GoToButton: Story = {
  args: {
    label: 'Go to Button stories',
    onClick: linkTo('Components/Button', 'Primary'),
  },
};

Navigate with custom handlers:

import { hrefTo } from '@storybook/addon-links';

export const WithLink: Story = {
  render: () => {
    const [href, setHref] = useState('');

    useEffect(() => {
      hrefTo('Components/Button', 'Primary').then(setHref);
    }, []);

    return <a href={href}>View Button</a>;
  },
};

4.6 CSS Pseudo-States

The @storybook/addon-pseudo-states addon lets you force CSS pseudo-states (:hover, :focus, :active, etc.) without actually interacting with the component — essential for visual testing.

npx storybook add @storybook/addon-pseudo-states
export const Hover: Story = {
  parameters: {
    pseudo: { hover: true },
  },
};

export const FocusAndActive: Story = {
  parameters: {
    pseudo: { focus: true, active: true },
  },
};

export const FocusVisible: Story = {
  parameters: {
    pseudo: { focusVisible: true },
  },
};

Supported pseudo-states: hover, active, focus, focusVisible, focusWithin, visited, link, target.

4.7 Portable Stories

Portable stories let you reuse your Storybook stories in external test frameworks (Jest, Vitest, Playwright, Cypress) without duplicating component setup.

import { composeStories } from '@storybook/react';
import * as stories from './Button.stories';

const { Primary, Secondary, Danger } = composeStories(stories);

// Use in any test framework:
test('renders primary button', () => {
  render(<Primary />);
  expect(screen.getByText('Primary Button')).toBeInTheDocument();
});

test('renders secondary button', () => {
  render(<Secondary />);
  expect(screen.getByText('Secondary Button')).toBeInTheDocument();
});

composeStories applies all decorators, args, and parameters from both the story and preview.js, giving you fully-configured components.

Compose a single story:

import { composeStory } from '@storybook/react';
import meta, { Primary as PrimaryStory } from './Button.stories';

const Primary = composeStory(PrimaryStory, meta);

4.8 Tags

Tags are metadata labels applied to stories that control behavior across Storybook features.

const meta: Meta<typeof Button> = {
  component: Button,
  tags: ['autodocs'],  // Generate docs page for this component
};

export const Primary: Story = {
  tags: ['!dev'],      // Exclude from dev sidebar (docs-only)
};

export const Experimental: Story = {
  tags: ['!autodocs'], // Exclude from auto-generated docs page
};

Built-in tags:

Tag Effect
autodocs Generate an automatic docs page
!autodocs Exclude from auto-generated docs
!dev Hide from the sidebar in dev mode
!test Exclude from test runs

Apply tags globally in preview.ts to enable autodocs for all stories:

const preview: Preview = {
  tags: ['autodocs'],
};

5. Common Tasks

5.1 Setting Up Storybook for a Next.js App Router Project

Goal: Get Storybook working with Next.js App Router, including support for next/image, next/link, next/navigation, and server components.

Prerequisites: An existing Next.js 13+ project with App Router.

Steps:

  1. Initialize Storybook with the Next.js framework:
npx storybook@latest init

Storybook auto-detects Next.js and installs @storybook/nextjs.

  1. Verify .storybook/main.ts uses the Next.js framework:
import type { StorybookConfig } from '@storybook/nextjs';

const config: StorybookConfig = {
  stories: [
    '../src/**/*.stories.@(js|jsx|ts|tsx)',
    '../app/**/*.stories.@(js|jsx|ts|tsx)',
  ],
  framework: '@storybook/nextjs',
  addons: ['@storybook/addon-docs'],
  staticDirs: ['../public'],  // Serve public/ for next/image paths
};

export default config;
  1. Add the App Router stories glob (note ../app/** above).

  2. If you use next/font, configure it in .storybook/preview.ts:

import '../app/globals.css';  // Import your global styles
  1. Start Storybook:
npx storybook dev

Verify: Storybook loads. Stories using next/image and next/link render correctly. Navigation mocks are provided automatically by @storybook/nextjs.

If it fails: Check that staticDirs includes '../public' so image assets resolve. If next/font fails, the @storybook/nextjs framework mocks font loading automatically — no extra config needed.

5.2 Setting Up Storybook with Next.js + Vite

Goal: Use Vite instead of Webpack for faster Next.js Storybook builds.

Prerequisites: Next.js 13+ project. Note that @storybook/nextjs-vite may have different Next.js feature coverage than the Webpack-based @storybook/nextjs.

Steps:

  1. Install the Vite-based Next.js framework:
npm install --save-dev @storybook/nextjs-vite
  1. Update .storybook/main.ts:
import type { StorybookConfig } from '@storybook/nextjs-vite';

const config: StorybookConfig = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
  framework: '@storybook/nextjs-vite',
  addons: ['@storybook/addon-docs'],
};

export default config;
  1. Start Storybook:
npx storybook dev

Verify: Storybook starts significantly faster than the Webpack-based setup. Hot module replacement (HMR) is near-instant.

5.3 Adding Accessibility Testing to Every Story

Goal: Automatically audit every story for WCAG violations.

Prerequisites: Storybook already installed.

Steps:

  1. Install the a11y addon:
npx storybook add @storybook/addon-a11y
  1. Verify it was added to .storybook/main.ts:
addons: [
  '@storybook/addon-docs',
  '@storybook/addon-a11y',  // ← Should be here
],
  1. Configure global a11y rules in .storybook/preview.ts:
const preview: Preview = {
  parameters: {
    a11y: {
      config: {
        rules: [
          // Disable rules that don't apply at the component level
          { id: 'landmark-one-main', enabled: false },
          { id: 'page-has-heading-one', enabled: false },
          { id: 'region', enabled: false },
        ],
      },
    },
  },
};
  1. Restart Storybook.

Verify: Open any story. The "Accessibility" panel in the bottom panel shows violations (red), passes (green), and incomplete checks (yellow).

If it fails: If the panel doesn't appear, restart Storybook with cache cleared: npx storybook dev --no-manager-cache.

5.4 Writing Interaction Tests with Play Functions

Goal: Test a multi-step form interaction that fills fields and submits.

Prerequisites: A form component with accessible labels.

Steps:

  1. Import test utilities from storybook/test:
import { expect, fn, userEvent, within } from 'storybook/test';
  1. Create a mock for the submit handler:
const meta: Meta<typeof ContactForm> = {
  component: ContactForm,
  args: {
    onSubmit: fn(),
  },
};
  1. Write the play function:
export const SuccessfulSubmission: Story = {
  play: async ({ canvasElement, args }) => {
    const canvas = within(canvasElement);

    await userEvent.type(canvas.getByLabelText('Name'), 'Jane Doe');
    await userEvent.type(canvas.getByLabelText('Email'), '[email removed]');
    await userEvent.type(
      canvas.getByLabelText('Message'),
      'Hello, this is a test message.'
    );

    await userEvent.click(canvas.getByRole('button', { name: /send/i }));

    await expect(args.onSubmit).toHaveBeenCalledOnce();
    await expect(canvas.getByText('Message sent!')).toBeInTheDocument();
  },
};
  1. Open the story in Storybook and check the Interactions panel.

Verify: The Interactions panel shows each step (type, click, assertion) with green checkmarks. If any assertion fails, the step turns red with an error message.

If it fails: Ensure your form elements have proper aria-label or associated <label> elements. getByLabelText won't find elements without accessible names.

5.5 Running Stories as Vitest Component Tests

Goal: Execute all stories in CI via Vitest with browser-mode rendering.

Prerequisites: Vite-based framework, Vitest 3.0.0+.

Steps:

  1. Add the Vitest addon:
npx storybook add @storybook/addon-vitest
  1. Install Playwright for browser-mode rendering:
npx playwright install chromium
  1. Create or update vitest.workspace.ts:
import { defineWorkspace } from 'vitest/config';
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';

export default defineWorkspace([
  'vitest.config.ts',
  {
    extends: 'vite.config.ts',
    plugins: [storybookTest()],
    test: {
      name: 'storybook',
      browser: {
        enabled: true,
        headless: true,
        provider: 'playwright',
        instances: [{ browser: 'chromium' }],
      },
      setupFiles: ['.storybook/vitest.setup.ts'],
    },
  },
]);
  1. Run the tests:
npx vitest --project storybook --run

Verify: All stories pass. Play functions run as assertions.

✓ src/components/Button.stories.tsx (3 tests) 245ms
✓ src/components/LoginForm.stories.tsx (2 tests) 512ms

Test Files  2 passed (2)
     Tests  5 passed (5)

If it fails: If MSW is installed, ensure it's version 2.0.0+. Older MSW versions are incompatible with the Vitest addon.

5.6 Adding Theme Switching

Goal: Let developers preview components in light and dark themes from the Storybook toolbar.

Prerequisites: Your app uses CSS classes or data attributes for theming.

Steps:

  1. Add the themes addon:
npx storybook add @storybook/addon-themes
  1. Configure in .storybook/preview.ts:

For CSS classes:

import { withThemeByClassName } from '@storybook/addon-themes';

const preview: Preview = {
  decorators: [
    withThemeByClassName({
      themes: {
        light: 'light',
        dark: 'dark',
      },
      defaultTheme: 'light',
    }),
  ],
};

For data attributes (e.g., Tailwind CSS dark mode):

import { withThemeByDataAttribute } from '@storybook/addon-themes';

const preview: Preview = {
  decorators: [
    withThemeByDataAttribute({
      themes: {
        light: 'light',
        dark: 'dark',
      },
      defaultTheme: 'light',
      attributeName: 'data-mode', // or 'class' for Tailwind
    }),
  ],
};
  1. Import your theme CSS in .storybook/preview.ts:
import '../src/styles/themes.css';
  1. Restart Storybook.

Verify: A theme switcher icon appears in the toolbar. Clicking it toggles between light and dark. All stories re-render with the selected theme.

5.7 Deploying a Static Storybook

Goal: Build Storybook as static HTML and deploy it to any hosting platform.

Prerequisites: A working Storybook dev setup.

Steps:

  1. Build the static output:
npx storybook build

Expected output:

info @storybook/core v8.x.x
info
info => Cleaning outputDir: storybook-static
info => Building manager..
info => Building preview..
info => Manager built (x.xx s)
info => Preview built (y.yy s)
info => Output directory: /path/to/project/storybook-static
  1. The output is in storybook-static/ (or your custom --output-dir). Preview locally:
npx http-server storybook-static
  1. Deploy to your platform:

GitHub Pages:

# In your CI workflow:
npx storybook build --output-dir docs
# Push docs/ to gh-pages branch

Netlify:

# Build command: npx storybook build
# Publish directory: storybook-static

Vercel:

# Build command: npx storybook build
# Output directory: storybook-static

S3 / CloudFront:

aws s3 sync storybook-static/ s3://your-bucket/ --delete
  1. Custom output directory:
npx storybook build --output-dir build/storybook

Verify: Open the deployed URL. The Storybook UI loads and all stories render correctly.

If it fails: If the build completes but the deployed site shows a blank page, check that your hosting platform serves index.html for all routes (SPA fallback).

5.8 Using Storybook with Tailwind CSS

Goal: Configure Storybook to use your Tailwind CSS setup.

Steps:

  1. Import your Tailwind CSS entry point in .storybook/preview.ts:
import '../src/styles/globals.css'; // or wherever your @tailwind directives are
  1. For Vite-based frameworks, Tailwind should work automatically if your tailwind.config.js content paths include story files.

  2. Update tailwind.config.js to include Storybook files:

module.exports = {
  content: [
    './src/**/*.{js,jsx,ts,tsx}',
    './.storybook/**/*.{js,jsx,ts,tsx}',  // Include Storybook files
  ],
};
  1. For Tailwind dark mode with Storybook's theme addon:
// .storybook/preview.ts
import { withThemeByClassName } from '@storybook/addon-themes';

const preview: Preview = {
  decorators: [
    withThemeByClassName({
      themes: {
        light: '',
        dark: 'dark',
      },
      defaultTheme: 'light',
    }),
  ],
};

Verify: Stories render with Tailwind utility classes applied.

5.9 Setting Up MSW (Mock Service Worker) for API Mocking

Goal: Mock API requests in stories so components that fetch data render consistently.

Steps:

  1. Install MSW:
npm install --save-dev msw
npx msw init public/ --save
  1. Create mock handlers:
// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('/api/users', () => {
    return HttpResponse.json([
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' },
    ]);
  }),
];
  1. Initialize MSW in .storybook/preview.ts:
import { initialize, mswLoader } from 'msw-storybook-addon';

initialize();

const preview: Preview = {
  loaders: [mswLoader],
};
  1. Use handlers in specific stories:
import { http, HttpResponse } from 'msw';

export const WithUsers: Story = {
  parameters: {
    msw: {
      handlers: [
        http.get('/api/users', () => {
          return HttpResponse.json([
            { id: 1, name: 'Alice' },
            { id: 2, name: 'Bob' },
          ]);
        }),
      ],
    },
  },
};

export const WithError: Story = {
  parameters: {
    msw: {
      handlers: [
        http.get('/api/users', () => {
          return new HttpResponse(null, { status: 500 });
        }),
      ],
    },
  },
};

Verify: Stories that fetch from /api/users receive mock data and render correctly.

5.10 Adding a Custom Webpack Alias

Goal: Use @/components/Button import aliases in stories.

Steps (Webpack-based frameworks):

// .storybook/main.ts
import path from 'path';

const config: StorybookConfig = {
  webpackFinal: async (config) => {
    config.resolve = {
      ...config.resolve,
      alias: {
        ...config.resolve?.alias,
        '@': path.resolve(__dirname, '../src'),
      },
    };
    return config;
  },
};

Steps (Vite-based frameworks):

// .storybook/main.ts
const config: StorybookConfig = {
  async viteFinal(config) {
    const { mergeConfig } = await import('vite');
    return mergeConfig(config, {
      resolve: {
        alias: {
          '@': '/src',
        },
      },
    });
  },
};

Verify: Stories using import { Button } from '@/components/Button' compile and render.

5.11 Organizing Stories with Autodocs

Goal: Auto-generate documentation for every component.

Steps:

  1. Enable autodocs globally in .storybook/preview.ts:
const preview: Preview = {
  tags: ['autodocs'],
};

Or per-component in the meta:

const meta: Meta<typeof Button> = {
  component: Button,
  tags: ['autodocs'],
};
  1. Add JSDoc comments to your component props for better docs:
interface ButtonProps {
  /** The text displayed inside the button */
  label: string;
  /** Visual style variant */
  variant?: 'primary' | 'secondary' | 'danger';
  /** Size of the button */
  size?: 'sm' | 'md' | 'lg';
  /** Whether the button is disabled */
  disabled?: boolean;
  /** Click event handler */
  onClick?: () => void;
}
  1. Each component with autodocs tag gets a "Docs" entry in the sidebar.

Verify: Open the "Docs" page for a component. It shows a rendered primary story, controls table with descriptions from JSDoc, and all story variants.

5.12 Migrating Between Storybook Versions

Goal: Upgrade from Storybook 7.x to 8.x (or any major version).

Steps:

  1. Run the upgrade command:
npx storybook@latest upgrade

This automatically:

  • Updates all @storybook/* packages to the latest version
  • Runs applicable codemods for breaking changes
  • Prompts for manual migration steps if needed
  1. If automatic migration isn't enough, run specific codemods:
npx storybook migrate <codemod-name> --glob="src/**/*.stories.tsx"
  1. Check for deprecation warnings:
npx storybook doctor
  1. Test the upgrade:
npx storybook dev
npx storybook build

Verify: All stories render. No console warnings about deprecated APIs.

If it fails: The doctor command identifies specific incompatibilities. Address them one at a time, starting with framework and addon mismatches.

5.13 Creating a Sandbox Project

Goal: Quickly spin up a fresh project with Storybook pre-configured for experimenting.

npx storybook sandbox

This prompts you to select a framework/builder combination and creates a ready-to-run project.

For a specific template:

npx storybook sandbox --template react-vite-ts

5.14 Writing Stories for Components with Context/Providers

Goal: Stories for components that depend on React Context (Router, Auth, Theme, Store).

Steps:

  1. Create a decorator that provides the context:
// .storybook/decorators/withProviders.tsx
import { MemoryRouter } from 'react-router-dom';
import { ThemeProvider } from 'styled-components';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient({
  defaultOptions: { queries: { retry: false } },
});

export const withProviders = (Story) => (
  <QueryClientProvider client={queryClient}>
    <MemoryRouter>
      <ThemeProvider theme={defaultTheme}>
        <Story />
      </ThemeProvider>
    </MemoryRouter>
  </QueryClientProvider>
);
  1. Apply globally in .storybook/preview.ts:
import { withProviders } from './decorators/withProviders';

const preview: Preview = {
  decorators: [withProviders],
};

Or per-story:

export const WithAuth: Story = {
  decorators: [
    (Story) => (
      <AuthProvider user={{ name: 'Jane', role: 'admin' }}>
        <Story />
      </AuthProvider>
    ),
  ],
};

5.15 Adding a Custom Toolbar Item

Goal: Add a dropdown to the toolbar for switching locales, viewport, or any custom global.

Steps:

  1. Define the global type in .storybook/preview.ts:
const preview: Preview = {
  globalTypes: {
    locale: {
      description: 'Internationalization locale',
      toolbar: {
        icon: 'globe',
        items: [
          { value: 'en', title: 'English', right: '🇺🇸' },
          { value: 'fr', title: 'Français', right: '🇫🇷' },
          { value: 'ja', title: '日本語', right: '🇯🇵' },
        ],
        title: 'Locale',
        dynamicTitle: true,
      },
    },
  },
  initialGlobals: {
    locale: 'en',
  },
};
  1. Use the global value in a decorator:
const preview: Preview = {
  decorators: [
    (Story, context) => {
      const locale = context.globals.locale;
      return (
        <I18nProvider locale={locale}>
          <Story />
        </I18nProvider>
      );
    },
  ],
};

Verify: The toolbar shows a globe icon. Clicking it reveals locale options. Selecting one re-renders all stories with the new locale.

5.16 Configuring Static Assets

Goal: Serve images, fonts, and other static files in stories.

// .storybook/main.ts
const config: StorybookConfig = {
  staticDirs: [
    '../public',                              // Serve entire public directory
    { from: '../assets/fonts', to: '/fonts' }, // Map specific directory
  ],
};

Reference in stories:

export const WithImage: Story = {
  render: () => <img src="/images/hero.jpg" alt="Hero" />,
};

5.17 Writing Stories for Vue 3 Components

Goal: Write stories for Vue 3 single-file components.

// Button.stories.ts
import type { Meta, StoryObj } from '@storybook/vue3';
import Button from './Button.vue';

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  tags: ['autodocs'],
  argTypes: {
    variant: {
      control: 'select',
      options: ['primary', 'secondary'],
    },
  },
};

export default meta;
type Story = StoryObj<typeof meta>;

export const Primary: Story = {
  args: {
    variant: 'primary',
    label: 'Click me',
  },
};

With slots:

export const WithSlot: Story = {
  render: (args) => ({
    components: { Button },
    setup() {
      return { args };
    },
    template: '<Button v-bind="args">Custom slot content</Button>',
  }),
  args: {
    variant: 'primary',
  },
};

5.18 Writing Stories for Angular Components

// button.stories.ts
import type { Meta, StoryObj } from '@storybook/angular';
import { ButtonComponent } from './button.component';

const meta: Meta<ButtonComponent> = {
  title: 'Components/Button',
  component: ButtonComponent,
  tags: ['autodocs'],
};

export default meta;
type Story = StoryObj<ButtonComponent>;

export const Primary: Story = {
  args: {
    variant: 'primary',
    label: 'Click me',
  },
};

// With module imports
export const WithForm: Story = {
  render: (args) => ({
    props: args,
    moduleMetadata: {
      imports: [FormsModule, ReactiveFormsModule],
    },
  }),
};

5.19 Writing Stories for Svelte Components

// Button.stories.ts
import type { Meta, StoryObj } from '@storybook/svelte';
import Button from './Button.svelte';

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  tags: ['autodocs'],
};

export default meta;
type Story = StoryObj<typeof meta>;

export const Primary: Story = {
  args: {
    variant: 'primary',
    label: 'Click me',
  },
};

5.20 Mocking Modules in Stories

Goal: Replace imported modules with mocks for isolated testing.

Use storybook/test mock utilities:

import { fn } from 'storybook/test';

// In meta: mock at the component level
const meta: Meta<typeof UserProfile> = {
  component: UserProfile,
  args: {
    onSave: fn(),
    onCancel: fn(),
  },
};

// In a specific story: override with behavior
export const SaveSuccess: Story = {
  args: {
    onSave: fn().mockResolvedValue({ success: true }),
  },
};

export const SaveFailure: Story = {
  args: {
    onSave: fn().mockRejectedValue(new Error('Network error')),
  },
};

5.21 Documenting a Design System

Goal: Build a comprehensive component library with Storybook as the documentation hub.

Steps:

  1. Create an introduction page:
{/* src/docs/Introduction.mdx */}
import { Meta } from '@storybook/blocks';

<Meta title="Getting Started/Introduction" />

# Acme Design System

Welcome to the Acme Design System. This Storybook documents every component,
token, and pattern available for building Acme products.

## Quick Links

- [Colors](?path=/docs/design-tokens-colors--docs)
- [Typography](?path=/docs/design-tokens-typography--docs)
- [Components](?path=/docs/components-button--docs)
  1. Organize the sidebar with meaningful titles:
Design Tokens/
  Colors
  Typography
  Spacing
  Shadows
Components/
  Button
  Input
  Select
  Card
Patterns/
  Forms
  Navigation
  Layouts
Pages/
  Dashboard
  Settings
  1. Use consistent story naming:
// Every component file follows the same pattern:
const meta: Meta<typeof Input> = {
  title: 'Components/Input',
  component: Input,
  tags: ['autodocs'],
};

export const Default: Story = { args: { placeholder: 'Type here...' } };
export const WithLabel: Story = { args: { label: 'Email' } };
export const WithError: Story = { args: { error: 'Required field' } };
export const Disabled: Story = { args: { disabled: true } };

5.22 Configuring Backgrounds

// .storybook/preview.ts
const preview: Preview = {
  parameters: {
    backgrounds: {
      default: 'light',
      values: [
        { name: 'light', value: '#ffffff' },
        { name: 'dark', value: '#1a1a2e' },
        { name: 'brand', value: '#0070f3' },
        { name: 'warm gray', value: '#f5f5f4' },
      ],
    },
  },
};

5.23 Composing Storybook Instances

Goal: Combine multiple Storybooks (e.g., from different teams or packages) into a single UI.

// .storybook/main.ts
const config: StorybookConfig = {
  refs: {
    'design-system': {
      title: 'Design System',
      url: 'https://design-system.example.com/storybook',
    },
    'shared-components': {
      title: 'Shared Components',
      url: 'https://shared.example.com/storybook',
    },
  },
};

The composed Storybooks appear as separate root nodes in the sidebar.

5.24 Conditional Story Rendering

Goal: Show different content based on the framework or global value.

export const ResponsiveLayout: Story = {
  parameters: {
    viewport: {
      defaultViewport: 'mobile1',
    },
    layout: 'fullscreen',
  },
};

export const DesktopLayout: Story = {
  parameters: {
    viewport: {
      defaultViewport: 'responsive',
    },
    layout: 'fullscreen',
  },
};

5.25 Using Storybook with Monorepos

Goal: Set up Storybook that pulls stories from multiple packages in a monorepo.

// .storybook/main.ts
const config: StorybookConfig = {
  stories: [
    '../packages/ui/src/**/*.stories.@(js|jsx|ts|tsx)',
    '../packages/forms/src/**/*.stories.@(js|jsx|ts|tsx)',
    '../packages/layout/src/**/*.stories.@(js|jsx|ts|tsx)',
    '../apps/web/src/**/*.stories.@(js|jsx|ts|tsx)',
  ],
  framework: '@storybook/react-vite',
};

Caution

When pulling stories from multiple packages, ensure all packages use compatible versions of shared dependencies (React, etc.). Version mismatches cause cryptic runtime errors like "Invalid hook call."

5.26 Custom Decorators for Layout Testing

Goal: Test components at different viewport widths without the viewport addon.

const withContainerWidth = (width: string) => (Story) => (
  <div style={{ width, margin: '0 auto', border: '1px dashed #ccc' }}>
    <Story />
  </div>
);

export const Mobile: Story = {
  decorators: [withContainerWidth('375px')],
};

export const Tablet: Story = {
  decorators: [withContainerWidth('768px')],
};

export const Desktop: Story = {
  decorators: [withContainerWidth('1200px')],
};

5.27 Setting Up TypeScript for Stories

Goal: Get full type safety and autocompletion in story files.

  1. Storybook provides types via each framework package:
import type { Meta, StoryObj } from '@storybook/react';
// or '@storybook/vue3', '@storybook/angular', '@storybook/svelte', etc.
  1. For satisfies syntax (TypeScript 4.9+):
const meta = {
  title: 'Components/Button',
  component: Button,
  tags: ['autodocs'],
} satisfies Meta<typeof Button>;

export default meta;
type Story = StoryObj<typeof meta>;
  1. Configure TypeScript in .storybook/main.ts:
const config: StorybookConfig = {
  typescript: {
    check: false,                     // Disable fork-ts-checker
    reactDocgen: 'react-docgen-typescript', // Full TS type extraction
    reactDocgenTypescriptOptions: {
      shouldExtractLiteralValuesFromEnum: true,
      propFilter: (prop) =>
        prop.parent ? !/node_modules/.test(prop.parent.fileName) : true,
    },
  },
};

5.28 Loaders — Async Data Fetching for Stories

Goal: Fetch data before a story renders.

export const WithUserData: Story = {
  loaders: [
    async () => ({
      user: await fetch('/api/current-user').then((r) => r.json()),
    }),
  ],
  render: (args, { loaded: { user } }) => (
    <UserProfile user={user} {...args} />
  ),
};

Loaders run before the story renders, and their return values are available via the loaded context property.

5.29 Diagnosing Common Issues with storybook doctor

npx storybook doctor

This command checks:

  • Version consistency across all @storybook/* packages
  • Configuration issues in .storybook/main.js
  • Dependency conflicts
  • Deprecated API usage

Run it after any upgrade or when something isn't working right.

5.30 Customizing the Sidebar Sort Order

// .storybook/preview.ts
const preview: Preview = {
  parameters: {
    options: {
      storySort: {
        method: 'alphabetical',
        order: [
          'Introduction',
          'Design Tokens',
          ['Colors', 'Typography', 'Spacing'],
          'Components',
          'Patterns',
          'Pages',
          '*', // Everything else alphabetically
        ],
      },
    },
  },
};

6. Troubleshooting

6.1 Startup and Build Errors

Error: "It looks like you are using a version of Node.js that is not supported"

Cause: Storybook enforces Node.js 20.19+ or 22.12+ at startup. Versions below this minimum — including all Node.js 18.x releases — are rejected.

Solution:

node --version  # Check current version
nvm install 22  # Install a supported version
nvm use 22      # Switch to it

Prevention: Pin your Node version in .nvmrc or package.json engines field.


Error: "Cannot find module '@storybook/react-vite'"

Cause: The framework package isn't installed, or there's a version mismatch.

Solution:

npm install --save-dev @storybook/react-vite@latest storybook@latest

If using a lockfile, do a clean install:

rm -rf node_modules package-lock.json
npm install

Error: "No configuration file found"

Cause: Storybook can't find .storybook/main.js or .storybook/main.ts.

Solution: Check the .storybook/ directory exists at your project root:

ls -la .storybook/

If missing, re-run init:

npx storybook@latest init

Error: "ERR_PACKAGE_PATH_NOT_EXPORTED"

Cause: Package export map conflict, typically from ESM/CJS interop issues with newer Node.js versions.

Solution:

rm -rf node_modules/.cache .storybook/.cache
npx storybook dev --no-manager-cache

If persistent, try adding to your package.json:

{
  "overrides": {
    "@storybook/core": "latest"
  }
}

Error: "FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory"

Cause: Large projects exceed Node.js default memory limit during build.

Solution:

NODE_OPTIONS="--max-old-space-size=8192" npx storybook build

For dev mode:

NODE_OPTIONS="--max-old-space-size=4096" npx storybook dev

Prevention: Add to package.json scripts:

{
  "scripts": {
    "storybook": "NODE_OPTIONS='--max-old-space-size=4096' storybook dev -p 6006",
    "build-storybook": "NODE_OPTIONS='--max-old-space-size=8192' storybook build"
  }
}

Error: "Storybook failed to build ... Could not resolve ..."

Cause: Missing dependency or incorrect import path in a story file.

Solution:

  1. Read the full error to identify the unresolvable import
  2. Install the missing package or fix the import path
  3. Check that path aliases (e.g., @/) are configured in both your app config AND .storybook/main.ts

"storybook dev" starts but shows a blank white page

Cause: The stories glob pattern doesn't match any files.

Solution: Check .storybook/main.ts:

// Verify this matches your actual file structure
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],

Manually verify story files exist:

find src -name "*.stories.*" -type f

Build succeeds but deployed site shows blank page

Cause: The hosting platform isn't configured for SPA routing, or the base URL is wrong.

Solution: For hosting platforms that need SPA fallback, add redirect rules. For custom base URLs:

npx storybook build --output-dir storybook-static

Ensure your hosting serves index.html for all routes under the Storybook path.


6.2 Story and Component Errors

Error: "Objects are not valid as a React child"

Cause: A story is passing an object where React expects a string, number, or element.

Solution: Check your args — you may be passing an object to a prop that expects a string:

// Wrong
export const Bad: Story = {
  args: {
    label: { text: 'Hello' },  // Object where string expected
  },
};

// Correct
export const Good: Story = {
  args: {
    label: 'Hello',
  },
};

Error: "Invalid hook call"

Cause: Multiple copies of React in the bundle, or hooks called outside a component.

Solution:

# Check for duplicate React
npm ls react

# If duplicates exist, add resolution
# package.json:
{
  "resolutions": {
    "react": "^18.0.0"
  }
}

For monorepos, ensure all packages reference the same React version.


Infinite loading spinner on a story

Cause: A known issue (#28382) when using dynamic imports between story files, or a story that throws during render.

Solution:

  1. Check the browser console for errors
  2. If using dynamic import() between story files, refactor to static imports
  3. Wrap the story in an error boundary decorator:
export const Problematic: Story = {
  decorators: [
    (Story) => (
      <ErrorBoundary fallback={<div>Story crashed</div>}>
        <Story />
      </ErrorBoundary>
    ),
  ],
};

Re-exported stories don't appear in sidebar

Cause: Known limitation (#32561). Re-exporting stories from another file isn't reliably supported.

Solution: Define stories directly in each .stories.tsx file instead of re-exporting from a shared file. Use composeStories for reusing story definitions in tests.


Controls panel shows "No controls" for a component

Cause: Storybook can't extract prop types from the component.

Solution:

  1. Ensure component is set in the meta:
const meta: Meta<typeof Button> = {
  component: Button,  // Must reference the actual component
};
  1. For TypeScript, try switching the docgen engine:
// .storybook/main.ts
typescript: {
  reactDocgen: 'react-docgen-typescript',
}
  1. Ensure props are exported/typed:
// Props interface must be on the component, not a separate file
export interface ButtonProps {
  label: string;
  variant?: 'primary' | 'secondary';
}

export const Button = ({ label, variant = 'primary' }: ButtonProps) => {
  // ...
};

Stories compile but component doesn't render

Cause: Missing CSS, missing provider/context, or an unhandled error.

Solution:

  1. Check the browser console (F12) for errors
  2. Import required CSS in .storybook/preview.ts
  3. Add necessary providers via decorators
  4. Check if the component requires props that aren't provided in args

6.3 Addon Errors

Addon panel doesn't appear after installation

Cause: The addon isn't listed in .storybook/main.ts, or the manager cache is stale.

Solution:

  1. Verify the addon is in main.ts:
addons: ['@storybook/addon-a11y'], // Must be listed here
  1. Clear the cache:
npx storybook dev --no-manager-cache

Error: "Addon 'X' is not compatible with Storybook Y"

Cause: Third-party addon hasn't been updated for your Storybook version.

Solution:

# Check for updates
npm outdated | grep storybook

# Upgrade everything
npx storybook@latest upgrade

# If the addon still isn't compatible, check its GitHub for Storybook 8 support

"@storybook/addon-vitest" errors: "Cannot find browser provider"

Cause: Playwright isn't installed for browser-mode Vitest.

Solution:

npm install --save-dev @playwright/test
npx playwright install chromium

"@storybook/addon-vitest" errors: "MSW version mismatch"

Cause: MSW versions below 2.0.0 are incompatible with the Vitest addon.

Solution:

npm install --save-dev msw@latest

6.4 Framework-Specific Issues

Next.js: next/image not rendering in stories

Cause: The image optimization API isn't available in Storybook.

Solution: The @storybook/nextjs framework automatically mocks next/image. If images still don't appear:

  1. Ensure staticDirs includes '../public'
  2. Check image paths are relative to the public directory

Next.js: useRouter returns null

Cause: Next.js router context isn't available by default.

Solution: @storybook/nextjs provides automatic router mocking. For custom routing needs:

export const OnSettingsPage: Story = {
  parameters: {
    nextjs: {
      router: {
        pathname: '/settings',
        query: { tab: 'profile' },
      },
    },
  },
};

Angular: "No provider for ..." error

Cause: Angular dependency injection requires explicit module configuration.

Solution: Use moduleMetadata in the story:

export const WithService: Story = {
  render: (args) => ({
    props: args,
    moduleMetadata: {
      imports: [HttpClientModule],
      providers: [UserService],
    },
  }),
};

Svelte: Component props not appearing in Controls

Cause: Svelte prop extraction requires additional configuration.

Solution: Ensure you're using @storybook/svelte-vite (not a generic setup). The framework package includes Svelte-specific docgen.


Vercel deployment: Code blocks render JSON instead of JSX

Cause: Known issue (#32967) with how Vercel's build environment handles MDX transformation.

Solution: Build locally and deploy the static output instead of using Vercel's build step:

npx storybook build
# Deploy storybook-static/ as static files

6.5 Configuration Errors

Error: "Invalid configuration: framework is required"

Cause: The framework field is missing from .storybook/main.ts.

Solution:

// .storybook/main.ts
const config: StorybookConfig = {
  framework: '@storybook/react-vite', // Required
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
};

Warning: "Duplicate stories with the same id"

Cause: Two stories resolve to the same sidebar path (same title + same export name).

Solution: Ensure every story has a unique combination of title (in meta) and export name:

// File 1: title: 'Components/Button'
export const Primary: Story = {};

// File 2: title: 'Components/Button' — CONFLICT if also exports Primary
// Fix: use a different title or export name

Warning: "No story files found matching ..."

Cause: The glob pattern in stories doesn't match any files on disk.

Solution: Double-check paths (they're relative to the .storybook/ directory, not the project root):

// If your structure is:
// project/
//   .storybook/main.ts
//   src/components/Button.stories.tsx

stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], // Correct
stories: ['./src/**/*.stories.@(js|jsx|ts|tsx)'],  // Wrong — looks inside .storybook/src/

Storybook ignores changes to main.ts

Cause: Configuration files are only read at startup.

Solution: Stop and restart Storybook after any changes to .storybook/main.ts:

# Ctrl+C to stop, then restart
npx storybook dev

6.6 Performance Issues

Dev server startup takes more than 60 seconds

Cause: Broad story globs, large node_modules, or slow Webpack compilation.

Solutions:

  1. Narrow story globs:
// Instead of
stories: ['../src/**/*.stories.*'],
// Use specific paths
stories: [
  '../src/components/**/*.stories.tsx',
  '../src/pages/**/*.stories.tsx',
],
  1. Switch to Vite if currently using Webpack — Vite's dev server is significantly faster.

  2. Use --no-manager-cache only when debugging. The cache speeds up repeated starts.


Hot module replacement (HMR) is slow

Cause: Complex decorator chains or large component trees.

Solution:

  1. Check for heavy global decorators that re-mount on every change
  2. Avoid importing the entire component library in preview.ts — only import what's needed
  3. For Webpack, consider @storybook/react-vite for faster HMR

Tip

When troubleshooting any Storybook issue, start with npx storybook doctor. It catches the most common problems automatically and suggests specific fixes.


7. FAQ

Getting Started

Q: Do I need to eject or modify my app's build configuration to use Storybook? No. Storybook runs in its own process with its own build pipeline. It reads your source files but doesn't modify your app's build. Your vite.config.ts or webpack.config.js is only used as a base that Storybook extends.

Q: Can I use Storybook without React? Yes. Storybook supports Vue 3, Angular, Svelte, Preact, HTML, Web Components, and more. Each has a dedicated framework package (e.g., @storybook/vue3-vite, @storybook/angular). The CSF format is the same across all frameworks.

Q: What's the difference between Vite and Webpack builders? Vite provides faster dev server startup and near-instant HMR because it uses native ES modules. Webpack is more mature with broader plugin compatibility. For new projects, Vite is recommended. If you have complex Webpack plugins that don't have Vite equivalents, use Webpack.

Q: Can I run Storybook alongside my development server? Yes. Storybook runs on its own port (default 6006) and doesn't interfere with your app's dev server. You can run both simultaneously.

Q: Does Storybook work with a monorepo? Yes. Point the stories glob in .storybook/main.ts at multiple package directories. All packages must share compatible versions of React (or your framework). See recipe 5.25 for details.

Stories and CSF

Q: What is Component Story Format (CSF)? CSF is the standard for writing stories. A .stories.tsx file exports a default meta object (component metadata) and named exports for each story (component states). CSF files are plain JavaScript/TypeScript modules — no proprietary syntax.

Q: Can I write stories in JavaScript instead of TypeScript? Yes. Use .stories.js or .stories.jsx files. TypeScript provides better autocompletion and type checking for args, but it's not required.

Q: How do I pass children/slots to a component in a story? Use the render function for full control over the component's children:

export const WithChildren: Story = {
  render: (args) => (
    <Card {...args}>
      <p>This is the card content</p>
    </Card>
  ),
};

Q: Can I share args between stories? Yes. Define default args in the meta object, and individual stories inherit and override them:

const meta: Meta<typeof Button> = {
  args: { size: 'md', variant: 'primary' }, // Shared defaults
};

export const Large: Story = {
  args: { size: 'lg' }, // Overrides size, inherits variant
};

Q: How do I create a story that shows multiple components together? Use the render function to compose multiple components:

export const FormLayout: Story = {
  render: () => (
    <form>
      <Input label="Name" />
      <Input label="Email" type="email" />
      <Button label="Submit" variant="primary" />
    </form>
  ),
};

Q: What's the difference between args and parameters? args are dynamic inputs passed as props to your component — users can change them via the Controls panel. parameters are static metadata that control Storybook's behavior (layout, backgrounds, addon settings) — they don't affect the component's props.

Configuration

Q: Where does Storybook look for configuration files? In the .storybook/ directory at your project root. The three config files are main.js|ts (project settings), preview.js|ts (rendering settings), and manager.js|ts (UI settings).

Q: Can I use TypeScript for configuration files? Yes. Storybook natively supports .ts config files (.storybook/main.ts, .storybook/preview.ts, .storybook/manager.ts). TypeScript configs provide better autocompletion via the StorybookConfig and Preview types.

Q: How do I change the default port? Pass the -p flag: npx storybook dev -p 9009. Or update package.json:

{ "scripts": { "storybook": "storybook dev -p 9009" } }

Q: How do I add path aliases (like @/components)? Configure them in viteFinal (Vite) or webpackFinal (Webpack) in .storybook/main.ts. See recipe 5.10 for complete examples.

Q: How do I expose environment variables to stories? Prefix them with STORYBOOK_:

STORYBOOK_API_URL=https://api.example.com npx storybook dev

Access in stories via process.env.STORYBOOK_API_URL. The STORYBOOK variable (no prefix) is automatically set to 'true' when running inside Storybook.

Documentation

Q: What's the difference between autodocs and MDX? Autodocs automatically generates a documentation page from your stories and component props — zero effort. MDX lets you write custom documentation pages mixing Markdown with embedded stories and controls — full control over layout and content. Use autodocs for component API reference; use MDX for guides, principles, and custom layouts.

Q: How do I write component descriptions that appear in docs? Add JSDoc comments directly above your component definition. Storybook's docgen extracts them automatically:

/** Primary button for user actions. Supports three variants. */
export const Button = (props: ButtonProps) => { ... };

Q: Can I create standalone documentation pages not tied to a component? Yes. Create an MDX file with only a <Meta title="..." /> block and no story references. It appears in the sidebar as a standalone page. See Section 4.2.

Testing

Q: Do I need Vitest to test stories? No. Play functions run directly in the Storybook UI and provide visual interaction testing without any external test runner. The Vitest addon is optional — it lets you run those same play functions headlessly in CI.

Q: Can I use Jest instead of Vitest? The official addon supports Vitest. For Jest, use composeStories from @storybook/react (portable stories) to render stories in Jest tests. See Section 4.7.

Q: How do I test that a callback was called with specific arguments? Use fn() from storybook/test:

const meta = { args: { onClick: fn() } };

export const Clicked: Story = {
  play: async ({ canvasElement, args }) => {
    await userEvent.click(within(canvasElement).getByRole('button'));
    await expect(args.onClick).toHaveBeenCalledWith(
      expect.objectContaining({ type: 'click' })
    );
  },
};

Q: Does the a11y addon catch all accessibility issues? No. The axe-core engine catches ~57% of WCAG violations automatically. It cannot detect issues that require human judgment (like meaningful alt text, logical tab order, or screen reader behavior). Use it as a first line of defense, not a complete audit.

Addons

Q: Where do I find third-party addons? The Storybook addon catalog is at storybook.js.org/addons. Addons are npm packages — install them and add to the addons array in .storybook/main.ts.

Q: Does addon order matter? Yes. The order in the addons array determines the tab order in the addon panel. The first addon's panel appears first.

Q: How do I disable an addon for a specific story? Most addons support a disable parameter:

export const NoA11y: Story = {
  parameters: { a11y: { disable: true } },
};

Deployment and CI

Q: Can I deploy Storybook as a static site? Yes. npx storybook build generates a self-contained static site in storybook-static/ that can be hosted on any web server, CDN, or static hosting platform (GitHub Pages, Netlify, Vercel, S3, etc.).

Q: How large is the built output? The base manager UI is ~2-3 MB. Total size depends on the number of stories and bundled assets. A typical project produces 5-20 MB of static output.

Q: How do I run Storybook tests in CI? Use the Vitest addon for headless test execution:

npx vitest --project storybook --run

Or use Chromatic for visual regression testing against a cloud baseline.