# Storybook User Guide

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

```bash
# 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:

```bash
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`):

```typescript
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 |

### 2.2 Automatic Installation (Recommended)

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

```bash
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

```bash
# 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

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

`.storybook/main.ts`:

```typescript
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

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

`.storybook/main.ts`:

```typescript
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)

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

`.storybook/main.ts`:

```typescript
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

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

`.storybook/main.ts`:

```typescript
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

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

`.storybook/main.ts`:

```typescript
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

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

`.storybook/main.ts`:

```typescript
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

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

`.storybook/main.ts`:

```typescript
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

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

`.storybook/main.ts`:

```typescript
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

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

`.storybook/main.ts`:

```typescript
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

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

`.storybook/main.ts`:

```typescript
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

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

#### Ember

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

#### React Native Web + Vite

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

#### Server + Webpack 5

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

### 2.4 Post-Install Verification

After installation, verify everything works:

```bash
# 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:

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

Run the diagnostics command if anything looks wrong:

```bash
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:**

```bash
# 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:

```bash
npm install react react-dom
npx storybook@latest init
```

Or specify the framework type explicitly during init:

```bash
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.

```bash
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:**

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

#### Port 6006 already in use

```bash
# 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`:

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

Test the glob:

```bash
# 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:**

```bash
# 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).

```bash
# 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:

```typescript
// .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:**

```bash
# 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`:

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

Then reinstall:

```bash
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

```typescript
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:

```typescript
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.

```typescript
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:**

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

**Fullscreen layout (for page-level stories):**

```typescript
parameters: {
  layout: 'fullscreen',
}
```

**Disable specific addon panels per-story:**

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

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

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

```typescript
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:

```bash
# In your shell or .env
STORYBOOK_API_URL=https://api.example.com
```

```typescript
// 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

```typescript
// .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

```typescript
// .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`)

```typescript
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',
  },
};
```

#### Sidebar organization

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

```typescript
// 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.

```typescript
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:**

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

**Component-level decorator (in meta):**

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

**Global decorator (in preview.js):**

```typescript
// .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:

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

#### Multiple components in one story

```typescript
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:

```typescript
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:

```typescript
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:**

```typescript
/**
 * 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:

````mdx
{/* 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:

````mdx
{/* 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:

```typescript
// .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:

```typescript
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:

```typescript
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:**

```bash
npx storybook add @storybook/addon-vitest
```

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

```typescript
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:

```bash
# 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.

```bash
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:**

```typescript
// 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:**

```typescript
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:**

```typescript
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:

```typescript
// .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:

```bash
npx storybook add @storybook/addon-themes
```

**CSS class-based theming:**

```typescript
// .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:**

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

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

### 4.5 Navigation and Links

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

```bash
npx storybook add @storybook/addon-links
```

**Link from one story to another:**

```typescript
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:**

```typescript
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.

```bash
npx storybook add @storybook/addon-pseudo-states
```

```typescript
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.

```typescript
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:**

```typescript
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.

```typescript
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:

```typescript
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:

```bash
npx storybook@latest init
```

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

2. Verify `.storybook/main.ts` uses the Next.js framework:

```typescript
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;
```

3. Add the App Router stories glob (note `../app/**` above).

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

```typescript
import '../app/globals.css';  // Import your global styles
```

5. Start Storybook:

```bash
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:

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

2. Update `.storybook/main.ts`:

```typescript
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;
```

3. Start Storybook:

```bash
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:

```bash
npx storybook add @storybook/addon-a11y
```

2. Verify it was added to `.storybook/main.ts`:

```typescript
addons: [
  '@storybook/addon-docs',
  '@storybook/addon-a11y',  // ← Should be here
],
```

3. Configure global a11y rules in `.storybook/preview.ts`:

```typescript
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 },
        ],
      },
    },
  },
};
```

4. 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`:

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

2. Create a mock for the submit handler:

```typescript
const meta: Meta<typeof ContactForm> = {
  component: ContactForm,
  args: {
    onSubmit: fn(),
  },
};
```

3. Write the play function:

```typescript
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();
  },
};
```

4. 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:

```bash
npx storybook add @storybook/addon-vitest
```

2. Install Playwright for browser-mode rendering:

```bash
npx playwright install chromium
```

3. Create or update `vitest.workspace.ts`:

```typescript
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'],
    },
  },
]);
```

4. Run the tests:

```bash
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:

```bash
npx storybook add @storybook/addon-themes
```

2. Configure in `.storybook/preview.ts`:

**For CSS classes:**

```typescript
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):**

```typescript
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
    }),
  ],
};
```

3. Import your theme CSS in `.storybook/preview.ts`:

```typescript
import '../src/styles/themes.css';
```

4. 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:

```bash
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
```

2. The output is in `storybook-static/` (or your custom `--output-dir`). Preview locally:

```bash
npx http-server storybook-static
```

3. Deploy to your platform:

**GitHub Pages:**

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

**Netlify:**

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

**Vercel:**

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

**S3 / CloudFront:**

```bash
aws s3 sync storybook-static/ s3://your-bucket/ --delete
```

4. Custom output directory:

```bash
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`:

```typescript
import '../src/styles/globals.css'; // or wherever your @tailwind directives are
```

2. For Vite-based frameworks, Tailwind should work automatically if your `tailwind.config.js` content paths include story files.

3. Update `tailwind.config.js` to include Storybook files:

```javascript
module.exports = {
  content: [
    './src/**/*.{js,jsx,ts,tsx}',
    './.storybook/**/*.{js,jsx,ts,tsx}',  // Include Storybook files
  ],
};
```

4. For Tailwind dark mode with Storybook's theme addon:

```typescript
// .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:

```bash
npm install --save-dev msw
npx msw init public/ --save
```

2. Create mock handlers:

```typescript
// 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' },
    ]);
  }),
];
```

3. Initialize MSW in `.storybook/preview.ts`:

```typescript
import { initialize, mswLoader } from 'msw-storybook-addon';

initialize();

const preview: Preview = {
  loaders: [mswLoader],
};
```

4. Use handlers in specific stories:

```typescript
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):**

```typescript
// .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):**

```typescript
// .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`:

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

Or per-component in the meta:

```typescript
const meta: Meta<typeof Button> = {
  component: Button,
  tags: ['autodocs'],
};
```

2. Add JSDoc comments to your component props for better docs:

```typescript
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;
}
```

3. 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:

```bash
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

2. If automatic migration isn't enough, run specific codemods:

```bash
npx storybook migrate <codemod-name> --glob="src/**/*.stories.tsx"
```

3. Check for deprecation warnings:

```bash
npx storybook doctor
```

4. Test the upgrade:

```bash
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.

```bash
npx storybook sandbox
```

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

For a specific template:

```bash
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:

```typescript
// .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>
);
```

2. Apply globally in `.storybook/preview.ts`:

```typescript
import { withProviders } from './decorators/withProviders';

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

Or per-story:

```typescript
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`:

```typescript
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',
  },
};
```

2. Use the global value in a decorator:

```typescript
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.

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

Reference in stories:

```typescript
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.

```typescript
// 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:**

```typescript
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

```typescript
// 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

```typescript
// 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:

```typescript
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:

````mdx
{/* 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)
````

2. Organize the sidebar with meaningful titles:

```
Design Tokens/
  Colors
  Typography
  Spacing
  Shadows
Components/
  Button
  Input
  Select
  Card
Patterns/
  Forms
  Navigation
  Layouts
Pages/
  Dashboard
  Settings
```

3. Use consistent story naming:

```typescript
// 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

```typescript
// .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.

```typescript
// .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.

```typescript
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.

```typescript
// .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.

```typescript
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:

```typescript
import type { Meta, StoryObj } from '@storybook/react';
// or '@storybook/vue3', '@storybook/angular', '@storybook/svelte', etc.
```

2. For `satisfies` syntax (TypeScript 4.9+):

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

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

3. Configure TypeScript in `.storybook/main.ts`:

```typescript
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.

```typescript
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`

```bash
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

```typescript
// .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:**

```bash
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:**

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

If using a lockfile, do a clean install:

```bash
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:

```bash
ls -la .storybook/
```

If missing, re-run init:

```bash
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:**

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

If persistent, try adding to your `package.json`:

```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:**

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

For dev mode:

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

**Prevention:** Add to `package.json` scripts:

```json
{
  "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`:

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

Manually verify story files exist:

```bash
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:

```bash
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:

```typescript
// 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:**

```bash
# 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:

```typescript
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:

```typescript
const meta: Meta<typeof Button> = {
  component: Button,  // Must reference the actual component
};
```

2. For TypeScript, try switching the docgen engine:

```typescript
// .storybook/main.ts
typescript: {
  reactDocgen: 'react-docgen-typescript',
}
```

3. Ensure props are exported/typed:

```typescript
// 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`:

```typescript
addons: ['@storybook/addon-a11y'], // Must be listed here
```

2. Clear the cache:

```bash
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:**

```bash
# 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:**

```bash
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:**

```bash
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:

```typescript
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:

```typescript
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:

```bash
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:**

```typescript
// .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:

```typescript
// 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):

```typescript
// 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`:

```bash
# 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:

```typescript
// Instead of
stories: ['../src/**/*.stories.*'],
// Use specific paths
stories: [
  '../src/components/**/*.stories.tsx',
  '../src/pages/**/*.stories.tsx',
],
```

2. Switch to Vite if currently using Webpack — Vite's dev server is significantly faster.

3. 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:

```typescript
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:

```typescript
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:

```typescript
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`:

```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_`:

```bash
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:

```typescript
/** 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`:

```typescript
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](https://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:

```typescript
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:

```bash
npx vitest --project storybook --run
```

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

---

_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. Unofficial; not affiliated with or reviewed by the project._
