- React
Storybookとは・使い方|React+Viteで導入からテストまで実測
結論
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種の直し方
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 以上と書かれています。
テレメトリーを止める(任意)
Storybook は使い方の統計を送ります。送りたくないときは、最初のコマンドより前に環境変数 STORYBOOK_DISABLE_TELEMETRY=1 を設定する。
React+Vite のプロジェクトを用意する
手元では create-vite 9.3.3 で react-ts のひな形を作り、npm install した(28パッケージ・17秒)。
Storybook を入れる
プロジェクトのルートで実行する。質問に答えるか、--yes で既定の答えにする。
起動する
package.json に追加された storybook スクリプトで開発サーバーが起動する。既定のポートは 6006。
手元では質問を出さずに入れるため、次のように実行しました(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アドオン」の節で扱います。
導入で増えたもの・変わったもの
| ファイル | 変化 | 中身 |
|---|---|---|
.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 が出ました。
サイドバー
Story の一覧。title の「/」で階層になる。上に「Get started」のチェックリストが出る。
Find components で検索
ツールバー
再描画・背景・グリッド・表示幅・計測・アウトライン・ズームの切り替え。
右端の Share で URL 共有
Canvas
iframe の中に Story を描く。/iframe.html?id=… を開くと Canvas だけを表示できる。
テストもこの iframe を使う
アドオンパネル
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)
失敗したときは、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いちばん使い回す部品から Story を書くボタン・入力欄・バッジ。状態ごとに1つずつ
- 2props にコメントを書き、autodocs を付けるDocs がそのまま仕様書になる
- 3フォームなど操作のある部品に play 関数を書く送信・エラー表示・無効状態
- 4CI で vitest --project=storybook を動かす描けない Story・失敗した play 関数を止める
- 5a11y.test を 'error' に上げる違反を直してから切り替える
- 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期連続の黒字決算。