結論

Storybookは、UIコンポーネントをアプリから切り離して1つずつ表示し、状態ごとの見た目の確認・ドキュメント・テストを1か所で行う開発ツールです。既存の React(Vite)プロジェクトなら npm create storybook@latest の1行で入り、npm run storybook で起動します。

この記事でわかること

  • React+Vite に入れて起動・ビルドした実測(時間・容量・ファイル数)
  • Story(CSF3)・args・Controls・Actions・Docs・play関数の書き方と画面
  • Vitestアドオンのテスト結果とエラー原文9種の直し方

2026年10月29日更新|Storybook 10.6.5・React 19.3・Vite 8.3・Chromium 153で実行確認

Storybook の中では、コンポーネントの「状態」を1つずつ Story として登録し、ブラウザで切り替えて見ます。同じ Story ファイルが、見た目の確認、ドキュメント、自動テストの3つに使われます。

flowchart TD
    S[Story ファイル<br>状態ごとに1つ] --> D[storybook dev<br>画面で確認]
    S --> T[vitest<br>Story をテスト]
    D --> P[Controls・Actions<br>Docs・a11y]
    T --> B[storybook build<br>静的サイトで共有]

よく調べられることの答えを先にまとめます。

知りたいこと 答え 詳しい節
Storybookとは UIコンポーネントを単体で表示・文書化・テストするツール Storybookとは
Storybookの使い方(導入) npm create storybook@latest → npm run storybook Storybookの導入
Story の書き方 default export に meta、named export ごとに1つの Story(CSF3) Storyの書き方
props を画面で変えたい args と argTypes を書くと Controls パネルで変えられる argTypesとControls
クリックなどのテスト Story に play 関数を書き、Interactions パネルか Vitest で実行 play関数
Provider が無いとエラー decorators で Provider を包む decoratorsとparameters
チームに共有したい storybook build の静的ファイルを置く(手元で10MB・74ファイル) storybook build
無料で使えるか 本体はオープンソースで無料。Chromatic は無料枠と有料プラン Chromatic・Visual tests

確認環境:Ubuntu 24.04.5 LTS、Node.js 22.23.4、npm 10.9.9。create-vite 9.3.3 で React+TypeScript のプロジェクト(React 19.3.1・Vite 8.3.5・TypeScript 7.0.2)を作り、create-storybook 10.6.5 で Storybook 10.6.5(@storybook/react-vite)と、docs・test・a11y の機能(@storybook/addon-docs・@storybook/addon-vitest・@storybook/addon-a11y・@chromatic-com/storybook 5.6.0)を入れました。テレメトリーは最初の npm 実行より前に環境変数 STORYBOOK_DISABLE_TELEMETRY=1 で止めています。開発サーバーは 127.0.0.1 の自分のポートで起動し、画面は Chromium 153.0.8010.12(playwright 1.64.1 から操作)で確かめ、Story のテストは Vitest 4.1.10 のブラウザモードで実行しました。Chromatic・Visual tests・MCP・料金・インストール要件・テレメトリーの仕様は Storybook と Chromatic の公式ドキュメントで確かめた範囲だけを書いています。外部のサービスには何も送っていません。図は mermaid 11.16.0 で描画を確かめています(確認日 2026年10月29日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Storybookとは・使い方|React+Viteで導入からテストまで実測」

Storybookとは(何ができるか・使う場面)

この節では、Storybook が何をするツールで、どんな場面で役に立つのかが分かります。

Storybook は、ボタン・フォーム・カードのような UI コンポーネントを、アプリ全体を動かさずに1つずつブラウザに表示する開発ツールです。 オープンソース(MIT ライセンス)で、React・Vue・Angular・Svelte・Web Components など多くのフレームワークに対応しています。

アプリの中でコンポーネントを確かめようとすると、「エラー表示を見るためにわざと API を失敗させる」「ログイン後の画面を見るためにログインする」といった手間がかかります。Storybook では、こうした状態を Story(ストーリー) として1つずつ登録しておき、サイドバーから選ぶだけで表示できます。

1状態ごとの見た目を確かめる

「成功」「警告」「失敗」「長い文字」「送信中」など、アプリでは出しにくい状態もすぐ表示できる。

2ドキュメントになる

props の型・説明・既定値の表(Docs)が、コードのコメントと型から自動で作られる。

3テストになる

Story をそのままテストとして実行できる。クリックや入力の操作(play 関数)、アクセシビリティの検査も同じ Story で行える。

4共有できる

静的サイトとしてビルドし、デザイナーやレビューをする人に URL で見せられる。

「Storybook」で検索すると別のものも出る

2025年からは、Google の Gemini の「Storybook」(文章や写真から絵本を作る機能)も同じ名前で検索に出ます。この記事で扱うのは、Web 開発で使う JavaScript のツール(storybook.js.org)の Storybook です。英語の storybook は「童話の本・絵本」という意味の一般的な言葉です。

Storybook の画面を作る2つの部分

Storybook の画面は、マネージャー(サイドバー・ツールバー・下のパネル)と、プレビュー(コンポーネントを描く iframe)の2つに分かれています。起動したときのログにも「597 ms for manager and 693 ms for preview」のように、2つが別々に出てきます。プレビューは iframe の中で動くので、コンポーネントの CSS が Storybook の画面に混ざりません。

Storybookの導入(React+Viteでnpm create storybook)

この節では、既存の React(Vite)プロジェクトに Storybook を入れる手順と、そのとき何が追加・変更されるのかが、実行結果で分かります。

Storybook は「既にあるフロントエンドのプロジェクト」に後から入れる道具です。 空のフォルダーではなく、React などのプロジェクトのルートで実行します。公式ドキュメントの要件(2026年10月29日時点)は、Node.js 20 以上・npm 10 以上、Vite 5 以上、TypeScript 4.9 以上、Vitest 3 以上です。Storybook 10 への移行ガイドでは Node.js 20.19 以上か 22.12 以上と書かれています。

1

テレメトリーを止める(任意)

Storybook は使い方の統計を送ります。送りたくないときは、最初のコマンドより前に環境変数 STORYBOOK_DISABLE_TELEMETRY=1 を設定する。

export STORYBOOK_DISABLE_TELEMETRY=1

2

React+Vite のプロジェクトを用意する

手元では create-vite 9.3.3 で react-ts のひな形を作り、npm install した(28パッケージ・17秒)。

npm create vite@latest app -- --template react-ts

3

Storybook を入れる

プロジェクトのルートで実行する。質問に答えるか、--yes で既定の答えにする。

npm create storybook@latest

4

起動する

package.json に追加された storybook スクリプトで開発サーバーが起動する。既定のポートは 6006。

npm run storybook

手元では質問を出さずに入れるため、次のように実行しました(53秒)。--features は公式の「Recommended」にあたる docs・test・a11y です。

export STORYBOOK_DISABLE_TELEMETRY=1
cd app
npx create-storybook@latest --yes --no-dev --package-manager npm --features docs test a11y
T  Initializing Storybook
•  Package manager: npm
•  Adding Storybook version 10.6.5 to your project
o  Framework detected: react-vite
|  Adding devDependencies:
|  - storybook@^10.6.5
|  - @storybook/react-vite@^10.6.5
|  - @chromatic-com/storybook
|  - @storybook/addon-vitest@^10.6.5
|  - @storybook/addon-a11y@^10.6.5
|  - @storybook/addon-docs@^10.6.5
|  - vitest
|  - @vitest/browser-playwright
|  - @vitest/coverage-v8
|  - playwright
|  Installing dependencies...: added 218 packages
o  Updating your Vite config file:
|  /dev/shm/t63/app/vite.config.ts
|  ✅ @chromatic-com/storybook
|  ✅ @storybook/addon-vitest
|  ✅ @storybook/addon-a11y
|  ✅ @storybook/addon-docs
|  Installing Playwright browser binaries (press "c" to abort)
x  An error occurred while installing Playwright browser binaries. Please
|  run the following command later: npx playwright install chromium
!  Storybook setup completed, but some non-blocking errors occurred.
|  To run Storybook, run npm run storybook. CTRL+C to stop.

最後の Playwright のブラウザの取得は、この環境では外部からのダウンロードが止められているため失敗しました(Storybook 自体の導入は完了)。テストで使うブラウザの指定は「Vitestアドオン」の節で扱います。

導入で増えたもの・変わったもの

218個追加されたパッケージReact+Vite だけの時は28個
170MBnode_modules の増加94MB → 264MB
53秒導入にかかった時間依存の取得を含む
ファイル 変化 中身
.storybook/main.ts 新規 Story の場所・アドオン・フレームワーク
.storybook/preview.tsx 新規 全 Story 共通の parameters(Controls の判定・a11y の扱い)
src/stories/ 新規 見本の Button・Header・Page と Configure.mdx
package.json 変更 storybook・build-storybook のスクリプトと devDependencies
vite.config.ts 変更 Vitest で Story を動かす storybookTest の設定
.gitignore 変更 *storybook.log と storybook-static を追加
vitest.shims.d.ts 新規 Vitest のブラウザモードの型

作られた .storybook/main.ts です。stories の場所に合うファイルが自動で読み込まれます。

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

const config: StorybookConfig = {
  "stories": [
    "../src/**/*.mdx",
    "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
  ],
  "addons": [
    "@chromatic-com/storybook",
    "@storybook/addon-vitest",
    "@storybook/addon-a11y",
    "@storybook/addon-docs"
  ],
  "framework": "@storybook/react-vite"
};
export default config;

package.json には次の2行が足されました。

"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"

React のひな形を作るところから順に知りたいときは、関連記事「React環境構築をViteで行う手順|create viteの使い方」で扱っています。

テレメトリー(使用状況の送信)を止める方法

公式ドキュメントによると、Storybook は実行したコマンド・Story の数・版・使っているフレームワークやアドオンなどを匿名で送ります。止める方法は3つあります。

方法 書き方 注意
環境変数 STORYBOOK_DISABLE_TELEMETRY=1(true でも可) いちばん早い時点から止まる
main.ts core: { disableTelemetry: true } main.ts を読む前の boot イベントは止まらない(公式)
CLI フラグ npm run storybook -- --disable-telemetry 毎回付ける

公式は =true と書いていますが、10.6.5 のソースコード(optionalEnvToBoolean)では "1" と "true"(大文字小文字は問わない)のどちらも有効、"0" と "false" は無効と判定していました。手元で Chromium から開いた間、ブラウザが 127.0.0.1 以外へ送ったリクエストは0件でした。

Storybookの起動と画面の見方(サイドバー・Canvas・パネル)

この節では、storybook dev を起動したときの出力と、画面の各部分の名前と役割が分かります。

127.0.0.1 の自分のポート(46363)で起動しました。--ci は「質問を出さず、ブラウザも開かない」CI 向けの指定です(storybook dev --help の説明)。

$ npx storybook dev -p 46363 --host 127.0.0.1 --ci --no-open
T  storybook v10.6.5
•  Starting...
|   Storybook ready!
|   - Local:                http://127.0.0.1:46363/
|   - On your network:      http://127.0.0.1:46363/
•  597 ms for manager and 693 ms for preview
測ったもの 結果
サーバーが起動するまで(1回目) manager 597ms・preview 693ms
サーバーが起動するまで(2回目) manager 277ms・preview 306ms
Chromium で URL を開いて最初の Story が見えるまで(初回) 7.2秒(Vite の依存の事前変換を含む)
同じポートで2つ目を起動したとき(--ci) 止まらずに 46364 で起動した

Chromium で開いた画面は、左にサイドバー、中央に Canvas(コンポーネントの表示)、下にアドオンのパネルが並びます。サイドバーには、見本の Example/Button・Header・Page と、この記事で作った Components/LoginForm・StatusBadge が出ました。

1

サイドバー

Story の一覧。title の「/」で階層になる。上に「Get started」のチェックリストが出る。

Find components で検索

2

ツールバー

再描画・背景・グリッド・表示幅・計測・アウトライン・ズームの切り替え。

右端の Share で URL 共有

3

Canvas

iframe の中に Story を描く。/iframe.html?id=… を開くと Canvas だけを表示できる。

テストもこの iframe を使う

4

アドオンパネル

Controls・Actions・Interactions・Visual tests・Accessibility のタブが並んだ。

右上で右側に移せる

Story の一覧は、サーバーの /index.json からも読めます。手元では22件(Story 17件・Docs 5件)で、play 関数がある Story には play-fn のタグが付いていました。

$ curl -s http://127.0.0.1:46363/index.json   # 抜粋して整形
v 5
components-loginform--docs          docs   Components/LoginForm  Docs           ['dev','test','manifest','autodocs']
components-loginform--empty         story  Components/LoginForm  Empty          ['dev','test','manifest','autodocs']
components-loginform--submit-success story Components/LoginForm  Submit Success ['dev','test','manifest','autodocs','play-fn']
components-statusbadge--success     story  Components/StatusBadge Success       ['dev','test','manifest','autodocs']
example-page--logged-in             story  Example/Page          Logged In      ['dev','test','manifest','play-fn']

Storyの書き方(CSF3・meta・args)

この節では、Story ファイルの基本の形(CSF3)と、args で props を渡す書き方が分かります。

Story ファイルは、default export に「どのコンポーネントの Story か」(meta)を書き、named export 1つが1つの Story になります。 この形を CSF(Component Story Format)といい、今の標準は、Story をオブジェクトで書く CSF3 です。

状態を色で示すバッジ(StatusBadge)を作り、その Story を書きました。まずコンポーネントです。/** */ のコメントは Docs の説明欄に使われます。

import './status-badge.css';

export type Status = 'success' | 'warning' | 'error';

export interface StatusBadgeProps {
  /** 表示する状態 */
  status: Status;
  /** バッジの文字 */
  label: string;
  /** 小さく表示する */
  compact?: boolean;
}

/** 処理の状態を色で示すバッジ */
export const StatusBadge = ({ status, label, compact = false }: StatusBadgeProps) => (
  <span className={`badge badge--${status}${compact ? ' badge--compact' : ''}`} role="status">
    {label}
  </span>
);

Story ファイル(StatusBadge.stories.tsx)です。

import type { Meta, StoryObj } from '@storybook/react-vite';
import { StatusBadge } from './StatusBadge';

const meta = {
  title: 'Components/StatusBadge',   // サイドバーの場所(省略するとファイルの場所から決まる)
  component: StatusBadge,
  tags: ['autodocs'],                // Docs ページを自動で作る
  argTypes: {
    status: { control: 'select', options: ['success', 'warning', 'error'] },
    compact: { control: 'boolean' },
  },
  args: { label: '完了', status: 'success' },   // 全 Story 共通の props
} satisfies Meta<typeof StatusBadge>;

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

export const Success: Story = {};
export const Warning: Story = { args: { status: 'warning', label: '確認待ち' } };
export const Failed: Story = { args: { status: 'error', label: '失敗' } };
export const Compact: Story = { args: { compact: true } };
書く場所 項目 役割
meta(default export) component 対象のコンポーネント。props の表(Docs・Controls)の元になる
meta title サイドバーの階層。ファイル間で重複させない
meta・Story args コンポーネントに渡す props。meta の値を Story で上書きできる
meta・Story argTypes Controls の入力の種類・選択肢・説明
meta・Story decorators・parameters Story の外側を包む処理と、アドオンへの設定
Story(named export) play 描画後に動かす操作と検証
  • Story の表示名は export 名から作られます。 LongLabel はサイドバーで「Long Label」、SubmitSuccess は「Submit Success」と出ました。URL の ID も export 名から作られ、components-statusbadge--success のようになります。
  • 表示名だけを変えたいときは、Story に name: '表示名' を書きます(ID は export 名のまま)。
  • satisfies Meta<typeof StatusBadge> と書くと、args の書き間違いが TypeScript のエラーになり、StoryObj<typeof meta> で meta の args が型に反映されます。生成された見本もこの形でした。

CSF2・storiesOf・CSF Next との関係

以前の Storybook では、Story を関数で書く CSF2 や、storiesOf() という書き方が使われていました。storiesOf は今の Storybook では読み込めません(エラーの原文は「よくあるエラー」の節)。公式ドキュメントでは、CSF2 から CSF3 へは storybook migrate csf-2-to-3 で変換できると説明しています。

さらに型を強くした CSF Next(CSF Factories) は、definePreview → preview.meta() → meta.story() の順につないで書く形です。2026年10月29日時点の公式ドキュメントでは「プレビュー機能」で、CSF 1・2・3 は当面サポートが続き非推奨ではない、と書かれています。新しく始めるなら、生成された見本と同じ CSF3 で書くのが確実です。

argTypesとControlsの使い方

この節では、Controls パネルで props を変える方法と、変えた値を Story として保存する方法が、画面の実物で分かります。

Controls は、args の値を画面で書き換えて、その場で見た目を確かめるパネルです。 何も書かなくても props の型から入力欄が作られ、argTypes で入力の種類を変えられます。

StatusBadge の Success を開いた Controls パネルには、次の3行が出ました(Chromium の画面から読み取り)。

Name Control 元になったもの
status* 選択(success・warning・error) argTypes の control: 'select'
label* 文字の入力欄(完了) props の型 string
compact 「Set boolean」ボタン argTypes の control: 'boolean'(未設定の状態)

「*」は必須の props です。status を error に変えると、Canvas の HTML が次のように変わり、URL にも値が入りました。

変える前: <span class="badge badge--success" role="status">完了</span>
変えた後: <span class="badge badge--error" role="status">完了</span>
URL:      http://127.0.0.1:46363/?path=/story/components-statusbadge--success&args=status:error

URL の &args=status:error ごと渡すと、相手も同じ状態で開けます。

変えた値を Story として保存する(Create new story)

開発サーバーで値を変えると、パネルの下に「You modified this story. Do you want to save your changes?」と出て、Update story・Create new story・Reset のボタンが並びました。Warning の label を「レビュー中」に変えて Create new story を押し、名前に InReview と入れると、Story ファイルの末尾に次のコードが書き足されました(diff の結果)。

> export const InReview: Story = {
>   args: {
>     label: "レビュー中",
>     status: "warning"
>   }
> };

画面は新しい Story(components-statusbadge--in-review)に移りました。ファイルが書き換わるので、コミットの前に差分を確かめます。 ビルドした静的サイトでは、この3つのボタンは出ませんでした(値を変えて見ることはできる)。

argTypes でよく使う指定

やりたいこと argTypes の書き方
選択肢から選ぶ { control: 'select', options: ['a', 'b'] }(radio・inline-radio も可)
色を選ぶ { control: 'color' }
数値をスライダーで { control: { type: 'range', min: 0, max: 100 } }
Controls に出さない { table: { disable: true } }
入力欄だけ消す { control: false }

生成された .storybook/preview.tsx には、名前が background・color で終わる props を色の入力、Date で終わる props を日付の入力にする controls.matchers が入っていました。

ActionsとDocs(autodocs)の使い方

この節では、イベント(クリックや送信)の記録を見る Actions と、props の表を自動で作る Docs の使い方が分かります。

Actions:コールバックが呼ばれたかを見る

args に fn() を渡すと、そのコールバックが呼ばれたときに Actions パネルに記録されます。 ログインフォーム(LoginForm)の onSubmit に fn() を渡し、正しい値で送信すると、Actions パネルに次のように出ました。

▶onSubmit: (1) [Object]
  ▶0: Object
Clear

開くと、送られた { email, password } が見られます。fn は storybook/test から読み込みます(以前の版の @storybook/test から場所が変わっています)。

Docs:props の表と全 Story の一覧

meta に tags: ['autodocs'] を書くと、サイドバーに「Docs」が足され、次の内容のページが自動で作られました(Chromium で表示した文字の抜粋)。

StatusBadge
処理の状態を色で示すバッジ
完了
Show code
Name      Description           Default  Control
status*   表示する状態 union      -        Choose option... success warning error
label*    バッジの文字 string     -
compact   小さく表示する boolean  false    Set boolean
STORIES
Success / Warning / Failed / Compact / Long Label

コンポーネントの /** */ コメントが説明文に、props のコメントが Description に、既定値(compact = false)が Default に入りました。コードにコメントを書くだけで、デザイナーや他の開発者が読める仕様書になります。 Markdown で自由に書きたいときは .mdx のファイルを作ります(見本の Configure.mdx がその例です)。

decoratorsとparameters(Providerが要るコンポーネント)

この節では、Context の Provider やレイアウトが無いと表示できないコンポーネントを、decorators で表示する方法が分かります。

decorators は Story の外側を包む関数で、Provider・テーマ・余白などを足すために使います。 parameters は、レイアウトや a11y などアドオンの動きを決める設定です。

ログイン中のユーザー名を出す UserMenu は、AuthContext が無いと例外を投げるようにしました。decorators を付けずに開くと、Canvas に次のように出ました。

UserMenu は AuthContext.Provider の中で使います

The component failed to render properly, likely due to a configuration issue in Storybook. Here are some common causes and how you can address them:

Missing Context/Providers: You can use decorators to supply specific contexts or providers, which are sometimes necessary for components to render correctly.

Storybook 自身が「Provider が足りないなら decorators を使う」と案内しています。meta に decorators を書きます。

import type { Meta, StoryObj } from '@storybook/react-vite';
import { AuthContext, UserMenu } from './UserMenu';

const meta = {
  title: 'Components/UserMenu',
  component: UserMenu,
  decorators: [
    (Story) => (
      <AuthContext.Provider value={{ name: '山田太郎' }}>
        <Story />
      </AuthContext.Provider>
    ),
  ],
} satisfies Meta<typeof UserMenu>;

export default meta;
export const Default: StoryObj<typeof meta> = {};

保存すると Canvas に「山田太郎 さん」と出て、ブラウザのコンソールのエラーは0件になりました。

書く場所 効く範囲 向いている例
.storybook/preview.tsx の decorators 全 Story(いちばん外側) テーマ・ルーター・状態管理の Provider・共通 CSS
meta の decorators そのファイルの Story そのコンポーネントだけが使う Context
Story の decorators その Story だけ(いちばん内側) 幅を狭めて折り返しを見る
parameters の layout 置いた範囲 'centered'・'fullscreen'・'padded'

公式ドキュメントによると、適用の順は「全体 → コンポーネント → Story」で、Story の decorators がいちばん内側になります。手元では StatusBadge の LongLabel に幅160pxで包む decorators を付け、長いラベルの折り返しを確かめました。

play関数のインタラクションテスト

この節では、Story にクリックや入力の操作を書き、結果を自動で確かめる play 関数の書き方と、失敗したときの表示が分かります。

play 関数は、Story が描かれた後に動く関数です。 Testing Library と同じ書き方で要素を探し、userEvent で操作し、expect で結果を確かめます。

import type { Meta, StoryObj } from '@storybook/react-vite';
import { expect, fn } from 'storybook/test';
import { LoginForm } from './LoginForm';

const meta = {
  title: 'Components/LoginForm',
  component: LoginForm,
  tags: ['autodocs'],
  args: { onSubmit: fn() },          // 呼ばれたかを記録するスパイ
  parameters: { layout: 'centered' },
} satisfies Meta<typeof LoginForm>;

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

export const SubmitSuccess: Story = {
  play: async ({ args, canvas, userEvent }) => {
    await userEvent.type(canvas.getByLabelText('メールアドレス'), 'taro@example.com');
    await userEvent.type(canvas.getByLabelText('パスワード'), 'password123');
    await userEvent.click(canvas.getByRole('button', { name: 'ログイン' }));
    await expect(args.onSubmit).toHaveBeenCalledWith({
      email: 'taro@example.com',
      password: 'password123',
    });
  },
};

export const ShortPassword: Story = {
  play: async ({ canvas, userEvent }) => {
    await userEvent.type(canvas.getByLabelText('メールアドレス'), 'taro@example.com');
    await userEvent.type(canvas.getByLabelText('パスワード'), 'short');
    await userEvent.click(canvas.getByRole('button', { name: 'ログイン' }));
    await expect(canvas.getByRole('alert')).toHaveTextContent('パスワードは8文字以上で入力します');
  },
};

Story を開くと play 関数が動き、Interactions パネルに手順と結果が出ました(within(…) の中の要素の表示は「#storybook-root」と短くしています)。

PASS   Scroll to end   LoginForm.stories.tsx
Interaction steps
✓ userEvent.type(within(#storybook-root).getByLabelText("メールアドレス"), "taro@example.com")
✓ userEvent.type(within(#storybook-root).getByLabelText("パスワード"), "password123")
✓ userEvent.click(within(#storybook-root).getByRole("button", { name: "ログイン" }))
✓ expect(onSubmit).toHaveBeenCalledWith({ email: "taro@example.com", password: "password123" })

パネルの上には、最初に戻る・1つ戻る・1つ進む・最後へ進む・やり直しのボタンが並び、操作を1ステップずつ再生できます。期待する文を「6文字以上」とわざと間違えると、FAIL になり次のように出ました。

FAIL   Scroll to error
expect(within(#storybook-root).getByRole("alert")).toHaveTextContent("パスワードは6文字以上で入力します")
expect(element).toHaveTextContent()

Expected element to have text content:
  パスワードは6文字以上で入力します
Received:
  パスワードは8文字以上で入力します

テストで見つかった不具合:type="email" で送信が止まる

メールアドレスに「taro」を入れて送信し、エラー文が出ることを確かめる InvalidEmail も書きました。ところが、エラー文の要素(role="alert")が見つからずに失敗しました。

FAIL
within(#storybook-root).getByRole("alert")
Unable to find an accessible element with the role "alert"

Here are the accessible roles:

  textbox:
  Name "メールアドレス":
  <input type="email" value="taro" />
  --------------------------------------------------
  button:
  Name "ログイン":
  <button type="submit" />

原因は、<input type="email"> のブラウザ自身の入力チェックです。「@」の無い値ではブラウザが送信を止めるため、React の onSubmit まで届いていませんでした。Chromium で同じ入力欄の状態を調べた結果です。

validity.typeMismatch: true
validationMessage: "Please include an '@' in the email address. 'taro' is missing an '@'."

自分のチェックとエラー文を出したいので、<form noValidate> を付けてブラウザのチェックを外すと、InvalidEmail も PASS になりました。アプリで手で試すと気付きにくい違いが、Story のテストで見つかった例です。

play 関数の書き方

避けるawait を付けずに userEvent・expect を呼ぶ。data-testid だけで要素を探す
→
代わりにすべてに await を付ける。getByRole・getByLabelText など、使う人に近い方法で探す

公式ドキュメントも、ロールやラベルでの検索を優先し、data-testid は最後の手段としている

play 関数の引数からは、canvas・userEvent・args のほか、操作をまとめる step、描画前に準備をする mount も受け取れます(公式)。getBy と findBy の違いなど、要素の探し方は関連記事「React Testing Library使い方|getBy・findByの違い」で扱っています。

Vitestアドオンで全Storyをテストする(CLI・カバレッジ・a11y)

この節では、すべての Story をコマンド1つでテストする方法と、実行結果・カバレッジ・アクセシビリティ検査の結果が分かります。

@storybook/addon-vitest を入れると、Story 1つが Vitest のテスト1つになります。 play 関数がある Story はその検証まで、play 関数が無い Story は「エラーなく描けるか」を確かめます。Vitest のブラウザモードで、本物のブラウザ(既定は Playwright の Chromium)を使って動きます。

npx vitest run --project=storybook

最初は、ブラウザが入っていないため次のエラーで止まりました。

Error: browserType.launch: Executable doesn't exist at /opt/pw-browsers/chromium_headless_shell-1248/chrome-headless-shell-linux64/chrome-headless-shell
╔════════════════════════════════════════════════════════════╗
║ Looks like Playwright was just installed or updated.       ║
║ Please run the following command to download new browsers: ║
║                                                            ║
║     npx playwright install                                 ║
╚════════════════════════════════════════════════════════════╝

ふつうは npx playwright install chromium で直ります。この環境では取得ができないため、手元にある Chromium を vite.config.ts の provider に指定しました。

browser: {
  enabled: true,
  headless: true,
  provider: playwright({
    launchOptions: { executablePath: process.env.CHROMIUM_PATH },
  }),
  instances: [{ browser: 'chromium' }],
},

直した後の結果です(--reporter=verbose。一部の行を省いています)。

 ✓ |storybook (chromium)| src/components/LoginForm.stories.tsx > Empty 842ms
 ✓ |storybook (chromium)| src/components/LoginForm.stories.tsx > Loading 94ms
 ✓ |storybook (chromium)| src/components/LoginForm.stories.tsx > Invalid Email 262ms
 ✓ |storybook (chromium)| src/components/LoginForm.stories.tsx > Submit Success 310ms
 ✓ |storybook (chromium)| src/components/LoginForm.stories.tsx > Short Password 229ms
 ✓ |storybook (chromium)| src/stories/Page.stories.ts > Logged In 139ms
 ✓ |storybook (chromium)| src/components/StatusBadge.stories.tsx > Success 159ms
 …
 Test Files  5 passed (5)
      Tests  18 passed (18)
   Duration  11.24s (transform 0ms, setup 3.69s, import 506ms, tests 3.38s, environment 0ms)
18件Story から作られたテスト5ファイル・play 関数は5件
11.2秒全テストの時間テスト本体は3.4秒
100%命令のカバレッジsrc/components の 22/22

失敗したときは、Vitest の出力にも「Click to debug the error directly in Storybook:」の行と、その Story を Interactions パネルで開く URL が出ました。--coverage を付けると、Story がどのコードを通ったかが表になります。

$ npx vitest run --project=storybook --coverage --coverage.include='src/components/**'
=============================== Coverage summary ===============================
Statements   : 100% ( 22/22 )
Branches     : 100% ( 12/12 )
Functions    : 100% ( 5/5 )
Lines        : 100% ( 21/21 )

Vitest 自体の使い方(モック・スパイ)は、関連記事「Vitestモックの使い方|vi.mock・vi.fn・vi.spyOnの使い分け表」で扱っています。

アクセシビリティ検査(addon-a11y)を失敗として扱う

Accessibility パネルは、axe-core で表示中の Story を検査します。LoginForm の SubmitSuccess は「Violations 0・Passes 9」でした。薄い灰色(#bbbbbb)の文字のボタンを Story にすると、「Color contrast(color-contrast)Serious」の違反が1件出ました。

生成された preview.tsx の a11y.test は 'todo' で、このときの Vitest は違反があっても合格でした。'error' に変えると、同じ Story が次のように失敗しました。

expect(received).toHaveNoViolations(expected)

Expected the HTML found at $('button[type="button"]') to have no violations:

<button type="button" style="color: rgb(187, 187, 187); background: rgb(255, 255, 255);">送信</button>

Received:

"Elements must meet minimum color contrast ratio thresholds (color-contrast)"

Fix any of the following:
  Element has insufficient color contrast of 1.91 (foreground color: #bbbbbb, background color: #ffffff, font size: 10.0pt (13.3333px), font weight: normal). Expected contrast ratio of 4.5:1
a11y.test の値 Vitest の結果(違反1件の Story) 向いている場面
'todo'(生成時の値) 合格。違反は画面のパネルにだけ出る 導入したばかりで違反が多い
'error' 失敗(コントラスト比 1.91 と表示) CI で違反を止めたい
'off' 検査しない 検査の対象外にしたい Story

test-runner との違い

以前は、起動中の Storybook に対して Jest と Playwright で Story をテストする @storybook/test-runner が使われていました。公式ドキュメントでは、Vitest アドオンは Storybook の起動が要らず、画面のテストウィジェットやエディタの拡張から実行でき、Vite を使うフレームワークだけが対象、と説明しています。Webpack を使うプロジェクトでは test-runner を使います。ブラウザ全体を通した E2E テストは、関連記事「Playwright入門|E2Eテストの書き方とCI実行まで実行結果つきで解説」で扱っています。

storybook buildの出力とサイズ(静的サイトで共有)

この節では、storybook build で何が作られ、どれくらいの大きさになるのか、アプリ本体のビルドに影響するのかが、実測で分かります。

npm run build-storybook(中身は storybook build)は、どの Web サーバーにも置ける静的サイトを storybook-static/ に出力します。

$ npx storybook build
|  storybook-static/assets/LoginForm.stories-DC6sjF7h.js      5.24 kB │ gzip:   1.75 kB
|  storybook-static/assets/axe-C4lPdR0t.js                  659.30 kB │ gzip: 187.88 kB
|  storybook-static/assets/blocks-Bc-RFTi9.js               747.57 kB │ gzip: 236.13 kB
|  storybook-static/assets/iframe-Bm-VfLKH.js             1,141.14 kB │ gzip: 325.56 kB
|  Vite ✓ built in 1.99s
!  (!) Some chunks are larger than 500 kB after minification.
o  Output directory: /dev/shm/t63/app/storybook-static
—  Storybook build completed successfully
測ったもの 結果
ビルドの時間(コマンド全体) 4.5秒
出力の合計・ファイル数 10MB・74ファイル
いちばん大きいファイル sb-manager/globals-runtime.js 3.3MB
フォルダー別 sb-manager 4.4MB・assets 3.8MB・sb-addons 1.3MB
アプリ本体(vite build)の dist 288KB(JS 222.44kB)。Story と Storybook の文字列は含まれていなかった
静的サイトで最初の Story が見えるまで 2.4秒(127.0.0.1 の python http.server から配信)

「500 kB を超えるチャンクがある」という警告は、Storybook の画面の部品(iframe・blocks・axe)が大きいために出たもので、アプリ本体の大きさとは別です。Story は Storybook 用のビルドにだけ入り、アプリのビルドには入りませんでした。

静的サイトでも、Controls で値を変える・Interactions で play 関数を動かす(PASS)・Docs を見ることはできました。ファイルに書き戻す Update story・Create new story のボタンは、開発サーバーだけの機能です。

公開先と CI の選択肢(公式ドキュメントの範囲)

  • 静的サイトなので、GitHub Pages・Netlify・Amazon S3 など、静的ファイルを置ける場所ならどこでも配信できる。
  • CI で時間を縮めるときは build-storybook -- --test(テスト用の軽いビルド)を使う。
  • Chromatic に公開すると、Storybook どうしをつなぐ Composition や、版ごとの履歴が使える(Component Publishing Protocol のレベル1)。

Storybookが手元に作るファイル(Gitに入れるもの・入れないもの)

起動すると、プロジェクトの中だけでなく、ホームフォルダーにも ~/.storybook/ が作られました。

場所 中身 いつ消えるか
~/.storybook/settings.json userSince・導入ガイドとチェックリストの状態 残る
~/.storybook/instances/<ID>.json 起動中の Storybook の pid・ポート・URL・作業フォルダー Storybook を止めると消えた
node_modules/.cache/storybook/ Vite で変換した依存のキャッシュ node_modules と一緒に消える
storybook-static/ ビルドの出力 .gitignore に入っている
debug-storybook.log 導入時のエラーの詳しいログ .gitignore の *storybook.log で除外
  • 1.storybook/ と Story ファイルは Git に入れる設定と Story はチームで共有するコード
  • 2storybook-static/ とログは入れない生成された .gitignore にすでに書かれている
  • 3Create new story の差分を見てからコミットする画面の操作で Story ファイルが書き換わる
  • 4見本の src/stories/ は不要なら消すTypeScript の未使用 import エラーの元にもなった

Chromatic・Visual tests・MCP(公式ドキュメントの範囲)

この節では、導入時に一緒に入る Chromatic のアドオンと Visual tests、AI エージェントと連携する MCP が、何をするもので費用がかかるのかが分かります。

Visual tests は、Story の見た目のピクセルを、前回承認した画像(ベースライン)と比べるテストです。 比べる処理は Storybook の開発チームが作ったクラウドサービス Chromatic で行われ、アカウントへのサインインが要ります(公式)。手元ではアドオンのタブ「Visual tests」が出ることだけを確かめ、サインインはしていません。

Chromatic のプラン 月額 1か月のスナップショット
Free 0ドル 5,000
Starter 179ドル 35,000
Pro 399ドル 85,000
Enterprise 問い合わせ 上限なし

料金は Chromatic の料金ページの2026年10月29日時点の値です。Storybook 本体と、この記事で使ったアドオン(docs・vitest・a11y)は無料で使えます。Visual tests を使わないなら、Chromatic のアカウントは要りません。 使わないアドオンは npx storybook remove @chromatic-com/storybook で外せます。

Storybook MCP(AI エージェントとの連携)

公式ドキュメントによると、@storybook/addon-mcp を入れると、AI のコーディングエージェントが MCP 経由で Storybook のコンポーネント情報を読み、Story の作成・テストの実行・アクセシビリティの検査を行えるようになります。2026年10月29日時点では「プレビュー」の扱いで、React 系のフレームワークでは features: { componentsManifest: true } で docs の機能も使えます。導入の質問(Recommended)にも「AI」の項目があります。手元では入れていません。

Storybookのよくあるエラー・つまずき(原文と直し方)

この節では、Storybook でよく出るエラーの原文と、原因・直し方が分かります。すべて手元でわざと起こした結果です。

出た文言(原文) 起こした操作 直し方
NoMetaError: CSF: missing default export default export の無い Story ファイル export default meta; を書く
Error: Duplicate stories with id: components-statusbadge--success 2つのファイルで同じ title と export 名 title をファイルごとに変える
Unexpected `storiesOf` usage … SB8 does not support `storiesOf`. 以前の storiesOf() で書いた CSF3 に書き直す
Failed to resolve import "./Missing" from "src/components/Broken.stories.tsx". Does the file exist? 無いファイルを import パス・ファイル名・大文字小文字を直す
Failed to fetch dynamically imported module 上の import 失敗の、ブラウザ側の表示 ターミナルの Vite のエラーを見る
The component failed to render properly Provider の無い Context を使った decorators で Provider を包む
browserType.launch: Executable doesn't exist ブラウザ無しで Vitest を実行 npx playwright install chromium
Unable to find an accessible element with the role "alert" type="email" のブラウザのチェックで送信が止まった noValidate を付ける
error TS6133: 'React' is declared but its value is never read. 見本の Button.tsx・Header.tsx を含めて npm run build 未使用の import React を消すか見本を消す

1つの壊れたファイルでサイドバー全体が出なくなる

default export の無いファイルを置いたとき、ターミナルには次のように出ました。

!  🚨 Unable to index ./src/components/NoDefault.stories.tsx:
|  NoMetaError: CSF: missing default export
|  /dev/shm/t63/app/src/components/NoDefault.stories.tsx (line 1, col 0)
|  More info: https://storybook.js.org/docs/writing-stories?ref=error#default-export
x  Change detection failed: Unable to index
|  ./src/components/NoDefault.stories.tsx

このとき /index.json は一覧ではなくエラーの文を返し、他の正しい Story も一覧に出なくなりました。 ファイルを直すか消すと、すぐに元に戻りました(index.json は 200)。Story が全部消えたように見えたら、ターミナルの「Unable to index」の行を探します。

Vite の設定の警告と TypeScript のエラー

起動のたびに次の警告が出ました。生成された vite.config.ts が __dirname を使っているためです。

(!) Your Vite config uses features that are unsupported by `configLoader: 'native'`, which is planned to become the default in a future major version of Vite:
  - `__dirname` (vite.config.ts:10:24). Use `import.meta.dirname` instead

const dirname = import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url)); に書き換えると出なくなりました。また、TypeScript 7.0.2 のひな形で npm run build(tsc -b)を実行すると、Storybook の見本の import React from 'react'; が未使用として TS6133 のエラーになりました。見本を消すか、その行を消すとビルドが通りました。

導入時の注意の表示

  • Could not resolve the postinstall hook of @chromatic-com/storybook, skipping its configuration.:表示の後に「configured」と出て、main.ts の addons にも入っていました。動きに問題はありませんでした。
  • 同じポートが使われているとき:--ci を付けた起動では止まらず、次のポート(46364)で起動しました。決まったポートでなければ困るときは --exact-port(help の説明は「Exit early if the desired port is not available」)を付けるか、先に動いている Storybook を止めます。

Storybookを導入する・しないの判断表

この節では、自分のプロジェクトに Storybook を入れるかどうかを決める目安が分かります。

Storybook が役に立つのは、「同じ部品を、いくつもの状態で、何人もが使う」ときです。 入れると node_modules が170MB増え、Story を書いて保つ手間も増えます。

入れると効く

共通コンポーネント・デザインシステムがある

デザイナーやレビュー担当と画面で確かめ合う

エラー・空・読み込み中など、出しにくい状態が多い

見た目の崩れやアクセシビリティを CI で止めたい

見送ってよい

画面が数枚で、部品の使い回しが少ない

試作品で、UI がすぐ作り直しになる

Story を書いて保つ人がいない

サーバーで HTML を作る画面が中心

やりたいこと 向いている道具 Storybook との関係
関数・hooks のロジックを確かめる Vitest・Jest の単体テスト Storybook は不要
コンポーネント1つの表示と操作 Storybook の play 関数・React Testing Library Story を書くならテストもそこで行える
状態ごとの見た目をそろえて見る Storybook いちばん得意な用途
見た目の差分を自動で比べる Chromatic の Visual tests など Story がそのまま比べる対象になる
ログインから購入までの流れ Playwright などの E2E テスト Storybook の対象外
API の応答を差し替えて表示 MSW+Storybook decorators と組み合わせる

API の応答を差し替える MSW の書き方は、関連記事「MSWの使い方|v3対応のハンドラ一覧とVitest・ブラウザ設定」で扱っています。

入れると決めたら、この順で進める

  1. 1いちばん使い回す部品から Story を書くボタン・入力欄・バッジ。状態ごとに1つずつ
  2. 2props にコメントを書き、autodocs を付けるDocs がそのまま仕様書になる
  3. 3フォームなど操作のある部品に play 関数を書く送信・エラー表示・無効状態
  4. 4CI で vitest --project=storybook を動かす描けない Story・失敗した play 関数を止める
  5. 5a11y.test を 'error' に上げる違反を直してから切り替える
  6. 6ビルドを共有し、必要なら Visual tests費用とスナップショット数を見積もってから

よくある質問

Q. Storybookとは何ですか? A. UI コンポーネントをアプリから切り離して1つずつ表示し、状態ごとの見た目の確認・ドキュメント作り・テストを行う開発ツールです。オープンソースで無料で使えます。

Q. Storybook の使い方を一言で言うと? A. プロジェクトのルートで npm create storybook@latest を実行して入れ、*.stories.tsx にコンポーネントの状態を書き、npm run storybook でブラウザに表示します。

Q. Storybook は React 以外でも使えますか? A. 使えます。公式ドキュメントの要件では、Vue 3・Angular・Svelte・Next.js・Preact・Lit・React Native なども対応しています。導入コマンドはフレームワークを自動で判定し、判定できないときは --type で指定します。

Q. Next.js でも使えますか? A. 使えます。Vite で動かす @storybook/nextjs-vite と、Webpack で動かす @storybook/nextjs があります。公式ドキュメントでは、Vitest アドオンを使うなら nextjs-vite が必要と書かれています。

Q. 無料で使えますか? A. 本体とこの記事で使ったアドオンは無料です。見た目の差分を比べる Visual tests は Chromatic のサービスを使い、2026年10月29日時点で月5,000スナップショットまで無料、それを超えると有料プランです。

Q. ポート番号は変えられますか? A. storybook dev -p 番号 で変えられます。生成されたスクリプトは 6006 です。手元では --ci を付けて同じポートで2つ目を起動すると、次のポートで起動しました。

Q. Story を書くのが手間です。減らす方法はありますか? A. meta に共通の args を書き、Story では違う所だけを書くと短くなります。開発サーバーの Controls で値を変えて「Create new story」を押すと、その値の Story がファイルに書き足されます。

Q. Storybook のテストと Vitest・Jest の単体テストは、どちらを書くべきですか? A. 役割が違います。関数やロジックは単体テスト、コンポーネントの表示と操作は Story の play 関数が向いています。Story を書くなら、表示の確認とテストを1つにまとめられます。

Q. storiesOf で書いた Story が動きません。 A. 今の Storybook は storiesOf を読み込みません。手元では「SB8 does not support `storiesOf`.」と出ました。CSF3(default export と named export)に書き直します。

Q. 最新版はいくつですか? Storybook 11 は出ていますか? A. 2026年10月29日時点の npm の latest は 10.6.5、next のタグは 11.0.0-alpha.8(開発中の版)でした。既存のプロジェクトを上げるときは、公式の npx storybook@latest upgrade を使います。

Q. テレメトリーは送られますか? 止められますか? A. 既定では匿名の使用状況が送られます。環境変数 STORYBOOK_DISABLE_TELEMETRY=1、main.ts の core.disableTelemetry、--disable-telemetry のどれかで止まります。main.ts を読む前の送信まで止めるなら環境変数です。

まとめ

  • Storybook は、UI コンポーネントを状態ごとの Story として登録し、表示・ドキュメント・テストを1か所で行うツール。React+Vite には npm create storybook@latest で入り、手元では218パッケージ・170MB 増えた。
  • Story は CSF3 で、default export に meta、named export に Story を書く。args と argTypes で Controls、fn() で Actions、tags: ['autodocs'] で Docs が使える。
  • Provider が要るコンポーネントは decorators で包む。play 関数で操作と検証を書くと、type="email" のブラウザのチェックで送信が止まる不具合のような、手で試すと気付きにくい違いが見つかった。
  • Vitest アドオンで全18 Story を11.2秒でテストでき、a11y.test を 'error' にするとコントラスト不足の Story を失敗にできた。
  • storybook build は4.5秒で10MB・74ファイルの静的サイトを作り、アプリ本体のビルドには Story は入らなかった。Visual tests を使うときだけ Chromatic の料金を見積もる。

Story のテストで使う Testing Library の探し方は関連記事「React Testing Library使い方|getBy・findByの違い」で、画面全体の流れのテストは関連記事「Playwright入門|E2Eテストの書き方とCI実行まで実行結果つきで解説」で扱っています。

参考・出典

確認日はいずれも 2026年10月29日です。

  • Storybook「Install Storybook」:https://storybook.js.org/docs/get-started/install
  • Storybook「Component Story Format (CSF)」:https://storybook.js.org/docs/api/csf
  • Storybook「CSF Next」:https://storybook.js.org/docs/api/csf/csf-next
  • Storybook「Decorators」:https://storybook.js.org/docs/writing-stories/decorators
  • Storybook「Interaction tests」:https://storybook.js.org/docs/writing-tests/interaction-testing
  • Storybook「Vitest addon」:https://storybook.js.org/docs/writing-tests/integrations/vitest-addon
  • Storybook「Visual tests」:https://storybook.js.org/docs/writing-tests/visual-testing
  • Storybook「Publish Storybook」:https://storybook.js.org/docs/sharing/publish-storybook
  • Storybook「Telemetry」:https://storybook.js.org/docs/configure/telemetry
  • Storybook「Migration guide for Storybook 10」:https://storybook.js.org/docs/releases/migration-guide
  • Storybook「MCP server overview」:https://storybook.js.org/docs/ai/mcp/overview
  • Chromatic「Pricing」:https://www.chromatic.com/pricing
  • npm「storybook」(dist-tags):https://www.npmjs.com/package/storybook

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Storybookとは・使い方|React+Viteで導入からテストまで実測」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ
参加受付中

AI駆動開発、 無料セミナー