この記事の要点
• StorybookのインストールとCSF 3.0でのストーリー記述
• Args・Controls・デコレータによるインタラクティブな開発
• アクセシビリティアドオンと静的ビルドの活用
このチュートリアルで学ぶこと
- Storybookのインストールと初期化
- CSF (Component Story Format) 3.0 でストーリーを書く
- Args / ArgTypes / Controls の使い方
- デコレータでテーマ・状態を切り替える
- アクセシビリティアドオン (a11y)
- インタラクションテスト
- 静的ビルドと公開
StorybookはUIコンポーネントを単独で開発・閲覧・テストするためのツールです。デザインシステム構築やプロパティの組み合わせ確認、E2Eから切り離したコンポーネント単体の品質保証に最適です。
前提条件・必要な環境
- Node.js 20以上
- React + Viteプロジェクト(無ければ新規作成)
- TypeScriptの基礎
- パッケージマネージャ npm / pnpm
新規プロジェクトを作る場合:
npm create vite@latest my-ui -- --template react-ts
cd my-ui
npm install
インストール / セットアップ
Storybookは公式CLIで一発導入できます。プロジェクトルートで実行:
npx storybook@latest init
CLIがプロジェクト構成を検出し、Vite + Reactであれば適切なフレームワークパッケージ (@storybook/react-vite) を自動選択します。
完了後、Storybookを起動:
npm run storybook
ブラウザで http://localhost:6006 が開きます。
追加されるファイル
.storybook/
├── main.ts # Storybook全体設定
└── preview.ts # グローバルパラメータ・デコレータ
src/stories/ # サンプルストーリー
.storybook/main.ts (例):
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-a11y',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
};
export default config;
基本概念
ストーリー(Story)
コンポーネントの「ある状態」を表す関数。1コンポーネントに対し複数のストーリーを書きます。
CSF 3.0
Component Story Formatの最新版。meta のdefault export と、各ストーリーをnamed exportで定義します。
ポイント: CSF 3.0ではmetaのdefault exportと各ストーリーのnamed exportで構成します。Argsでpropsを定義し、Storybook UIからインタラクティブに変更できます。
Args
コンポーネントに渡すpropsの集合。Storybook UIから動的に変更できます。
Decorators
ストーリーをラップしてテーマプロバイダやルーター等を提供する関数。
ステップバイステップ実装
シンプルなButtonコンポーネントを作り、Storybookで管理していきます。
Step 1: Buttonコンポーネントを作る
src/components/Button.tsx:
import type { ButtonHTMLAttributes, ReactNode } from 'react';
export type ButtonVariant = 'primary' | 'secondary' | 'danger';
export type ButtonSize = 'sm' | 'md' | 'lg';
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ButtonVariant;
size?: ButtonSize;
children: ReactNode;
}
const variantClass: Record<ButtonVariant, string> = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
danger: 'bg-red-600 text-white hover:bg-red-700',
};
const sizeClass: Record<ButtonSize, string> = {
sm: 'px-2 py-1 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
};
export function Button({
variant = 'primary',
size = 'md',
children,
...rest
}: ButtonProps) {
return (
<button
className={`rounded font-medium ${variantClass[variant]} ${sizeClass[size]}`}
{...rest}
>
{children}
</button>
);
}
Step 2: 最初のストーリー
src/components/Button.stories.tsx:
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'danger'],
},
size: {
control: 'radio',
options: ['sm', 'md', 'lg'],
},
onClick: { action: 'clicked' },
},
args: {
children: 'Click me',
variant: 'primary',
size: 'md',
},
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = {};
export const Secondary: Story = {
args: {
variant: 'secondary',
},
};
export const Danger: Story = {
args: {
variant: 'danger',
children: 'Delete',
},
};
export const Large: Story = {
args: {
size: 'lg',
children: 'Large Button',
},
};
export const Disabled: Story = {
args: {
disabled: true,
children: 'Disabled',
},
};
Storybookに切り替えると Components/Button 配下に5つのストーリーが表示されます。Controlsパネルでpropsを動的に変更できます。
Step 3: デコレータでラップ
全ストーリーに共通の余白を付けたいとき、.storybook/preview.ts でグローバルデコレータを追加します。
import type { Preview } from '@storybook/react';
import React from 'react';
const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
decorators: [
(Story) => (
React.createElement('div', { style: { padding: 16 } }, React.createElement(Story))
),
],
};
export default preview;
Step 4: a11yアドオンで自動チェック
@storybook/addon-a11y がインストール済みなら、各ストーリーの下部 Accessibility タブにaxeの結果が表示されます。例えば、Button内のテキストを空にして実行すると、ボタンに名前が無い旨の警告が出ます。
Step 5: インタラクションテスト
@storybook/addon-interactions を活用し、ストーリー内でユーザー操作と検証を記述できます。
import { within, userEvent, expect } from '@storybook/test';
export const Clickable: Story = {
args: { children: 'Tap me' },
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole('button', { name: /tap me/i });
await userEvent.click(button);
await expect(button).toBeInTheDocument();
},
};
Interactions パネルにステップが表示され、再生・巻き戻しできます。
Step 6: MDXでドキュメントを書く
src/components/Button.mdx:
import { Meta, Story, Canvas } from '@storybook/blocks';
import * as ButtonStories from './Button.stories';
# Button
汎用ボタンコンポーネント。デザインシステムの基本要素です。
Step 7: 静的ビルド
npm run build-storybook
storybook-static/ 配下に静的ファイルが出力されます。GitHub PagesやNetlify、ChromaticなどにそのままデプロイできるUIカタログとして公開できます。
完成コード全体
my-ui/
├── .storybook/
│ ├── main.ts
│ └── preview.ts
├── src/
│ └── components/
│ ├── Button.tsx
│ ├── Button.stories.tsx
│ └── Button.mdx
├── package.json
└── vite.config.ts
npm run storybook で開発、npm run build-storybook で静的出力、という流れになります。
よくあるエラーと対処
Cannot find module '@storybook/react-vite'
Storybookの初期化が完了していないか、依存が壊れています。一度クリーンインストールしましょう。
rm -rf node_modules package-lock.json
npm install
スタイルが当たらない (Tailwindの場合)
.storybook/preview.ts でグローバルCSSをimportする必要があります。
import '../src/index.css';
autodocs が表示されない
tags: ['autodocs'] をmetaに設定し、addon-essentials がインストールされていることを確認してください。
型エラー: Meta<typeof Button>
Storybookとフレームワークパッケージのバージョンが揃っていない可能性があります。@storybook/react と @storybook/react-vite を同じバージョンに合わせましょう。
ベストプラクティス
- 1コンポーネント1ストーリーファイル: 関連するバリエーションをまとめる
- 状態を網羅する: hover/disabled/loading/error などすべて作る
- デザイントークンを活用: テーマプロバイダをデコレータで適用してダークモード切替を可能に
- a11y を CIに組み込む: 自動テストでアクセシビリティ違反を検知
- インタラクションを書く: 単純な表示確認だけでなくクリックや入力もテスト
- Visual Regression: ChromaticやLoki等で見た目の差分を自動検知
- モックは軽く: Storybook内でデータをfetchするときは
msw-storybook-addonを使う
次のステップ(発展課題)
- Form系コンポーネント(Input、Select、Checkbox)を追加してデザインシステム化
- ダークモードを
@storybook/addon-themesで切り替え - Chromaticで Visual Regression を導入
- MSWでAPIモック付きのストーリーを書く
- Test Runner (
@storybook/test-runner) でCI上の自動テスト - i18n対応: 言語切り替えデコレータを作る
- Figma連携:
@storybook/addon-designsでデザインカンプを並べる
まとめ
実践メモ: a11yアドオンを有効にすると、ストーリー上でアクセシビリティ違反を自動検出できます。早期発見でユーザビリティを向上させましょう。
Storybookを導入すると、コンポーネント開発のフィードバックループが劇的に短縮されます。
- propの組み合わせを目視で確認できる
- アクセシビリティ違反を早期発見
- デザイナーやPMとのコミュニケーション資料になる
- ドキュメントとテストの両方を兼ねられる
UIライブラリを育てるなら、最初の一歩としてStorybookを入れる価値は十分あります。
補足: 複数フレームワーク対応
Storybookは React 以外にも Vue, Svelte, Angular, Web Components, Solid に対応しています。フレームワークごとに公式パッケージが用意されています。
| フレームワーク | パッケージ |
|---|---|
| React + Vite | @storybook/react-vite |
| React + Webpack | @storybook/react-webpack5 |
| Vue 3 + Vite | @storybook/vue3-vite |
| Svelte + Vite | @storybook/svelte-vite |
| Angular | @storybook/angular |
npx storybook@latest init がプロジェクトを自動検出して適切なパッケージを選んでくれるので、基本は意識しなくて大丈夫です。
補足: テーマ切り替えデコレータ
ダークモードを切り替えたい場合、@storybook/addon-themes を導入してデコレータを設定します。
npm install -D @storybook/addon-themes
.storybook/preview.ts:
import { withThemeByClassName } from '@storybook/addon-themes';
export const decorators = [
withThemeByClassName({
themes: {
light: 'light',
dark: 'dark',
},
defaultTheme: 'light',
}),
];
ツールバーからテーマを切り替えてストーリーが正しく追従するか確認できます。
補足: CIでの自動テスト
@storybook/test-runner を使えば、ヘッドレスブラウザで全ストーリーをスモークテストできます。
npm install -D @storybook/test-runner
package.json:
{
"scripts": {
"test-storybook": "test-storybook"
}
}
GitHub Actions の例:
- run: npm run build-storybook
- run: npx http-server storybook-static --port 6006 &
- run: npx wait-on http://127.0.0.1:6006
- run: npm run test-storybook
各ストーリーがレンダリングできるか、play 関数のインタラクションが成功するかを CI でチェックできます。
補足: モックAPIとの連携
ネットワーク呼び出しを伴うコンポーネントをStorybookで動かすには、msw-storybook-addon が便利です。
npm install -D msw msw-storybook-addon
ストーリーごとに別のレスポンスを返すよう設定でき、ローディング・成功・エラー状態をすべてカタログ化できます。
参考リソース
- Storybook Documentation
- Storybook GitHub Repository
- Storybook Tutorials
- Component Story Format 3.0
- Storybook Addons Catalog