Storybook

UI コンポーネントをアプリ本体から切り離した環境で描画し、状態ごとの見た目と操作を確認・テスト・文書化する開発環境

フロントエンドテスト
Storybook」の技術書を見る →

Storybook とは

Storybook は、UI コンポーネントをアプリ本体から切り離して描画するための開発環境である。アプリとは別のビルドを立て、対象のコンポーネントだけをマウントして表示するので、ログインや画面遷移をたどらずに目的の状態を直接出せる。この 1 つの状態を Story と呼び、名前を付けて並べたものがカタログになる。

利点と代償は同じ性質から来る。ルーティングやデータ取得を通らないから速く確実に再現できる一方、コンポーネントが本来受け取っていた依存 (Context、ルーター、通信の応答) は自分で用意しなければ再現されない。差し替えが増えるほど、Storybook で緑なのに本番では崩れるという乖離の余地も広がる。

Story の書き方

Storybook が解釈するのは、このファイルの export の形である。default export の meta が対象コンポーネントと共通設定を宣言し、名前付き export のそれぞれが 1 つの Story になる。この規約を CSF (Component Story Format) と呼ぶ。

args はコンポーネントに渡す入力で、Story ごとに違う部分だけを書けばよい。tags: ['autodocs'] を付けると、型定義と args から説明ページが生成される。ファイルの正体は単なる ES モジュールなので、Storybook の外 (単体テストなど) からも import して同じ引数で描画できる。

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

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

type Story = StoryObj<typeof Button>;

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

export const Secondary: Story = {
  args: { variant: 'secondary', children: 'Cancel' },
};

export const Disabled: Story = {
  args: { variant: 'primary', children: 'Submit', disabled: true },
};

export const Loading: Story = {
  args: { variant: 'primary', children: 'Saving...', loading: true },
};

メリット

API やルーティングなしでコンポーネントを独立して開発でき、正常・エラー・ローディング・空状態を一覧で確認できる。Props の自動ドキュメント生成、スクリーンショット比較によるビジュアルテスト、Chromatic を使ったデザイナーとの協業も可能だ。

インタラクションテスト

Story に play を付けると、描画が終わった直後にその操作が自動で流れる。中で使うのは操作を模した API とアサーションで、Storybook 9 以降は本体パッケージの storybook/test から import する (@storybook/test は 8 系で更新が止まっている。2026 年 8 月時点の最新は 10 系で、10.0.0 は 2025 年 10 月の公開)。手で触って確かめていた確認手順を Story 側に書き移せるので、状態の再現とその後の操作を 1 か所にまとめられる。

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

export const Submitted: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    await userEvent.type(canvas.getByLabelText('Email'), 'alice@example.com');
    await userEvent.click(canvas.getByRole('button', { name: 'Submit' }));
    await expect(canvas.getByText('送信完了')).toBeInTheDocument();
  },
};

Chromatic (ビジュアルリグレッションテスト)

ビジュアルリグレッションテストは、Story ごとに撮った画像を前回の結果と比べ、差分を人に見せる仕組みである。Chromatic はこれを Storybook の配信と組み合わせた外部サービスで、CI 側の仕事はビルドして送ることだけになる。

# GitHub Actions で Chromatic を実行
- uses: chromaui/action@v18 # 2026 年 8 月時点の最新メジャー
  with:
    projectToken: ${{ secrets.CHROMATIC_TOKEN }}

PR ごとに画像を比較し、意図しない表示の変化を拾う。ただし差分の良し悪しは機械には決められないため、承認するか却下するかは人が判断する。この承認を運用に組み込まないと差分が出たまま放置され、やがて誰も結果を見なくなる。

テストピラミッドでの位置づけ

同じ画面を扱っていても、層ごとに確かめている対象が違う。

テストツール確かめる対象
単体テストVitest入力に対する出力・状態の遷移
コンポーネントテストStorybookコンポーネント 1 つの表示と操作
ビジュアルリグレッションChromatic 等前回との見た目の差分
E2E テストPlaywright画面をまたぐ導線と本物の依存

Storybook が受け持つのはコンポーネント 1 つに閉じた範囲である。複数画面をまたぐ流れや実際の通信を含む挙動は E2E の担当で、そこまで Storybook で代替しようとすると、依存を差し替える仕掛けの保守が本体の開発より重くなる。

Story を腐らせないための線引き

Story はアプリの状態を再現する場所ではなく、入力の組み合わせを並べる場所と割り切る。1 つの Story に画面遷移や複数段の通信を詰め込むと、失敗したときに何が壊れたのか切り分けられなくなる。

導入して数か月で価値が落ちる原因は、たいてい Story の数ではなく更新の途切れである。コンポーネントを直したときに Story も直す動機を作るには、Story をテストの入力として使い回すのが手堅い。単体テストが同じ Story を読んでいれば、放置された Story はテストの失敗として表に出る。

見た目の差分は、承認する経路を先に決めてから導入する。判断する人が決まっていない差分検出は、警告が積み上がるだけで何も守らない。

この記事は役に立ちましたか?

関連用語

関連する記事