こんにちは。
株式会社アドグローブ ソリューション第一事業部の清水です!
突然ですがフロントエンド開発で、こんなお悩みありませんか?
そんな悩みを解決してくれるのが Storybook です!
特に、フロントエンド開発でコンポーネント管理に課題を感じている方におすすめです。
この記事では、Storybookの導入方法やメリットを紹介します!
Storybookとは?
Storybookは、ReactやVueなどのフレームワークで使える UIコンポーネント管理ツール です!
Storybookを使えば、UIコンポーネントを単体で表示・動作確認できます。
また、コンポーネントごとにテストやドキュメントを管理でき、開発チーム内での共有がスムーズになります。
Storybookを導入するメリット
Storybookを導入すると、以下のようなメリットがあります。
- コンポーネントの動作確認が簡単になり、デバッグ効率が向上する
- デザイナーやPMもUIを直接確認でき、フィードバックがスムーズになる
- コンポーネントのテスト・仕様を一元管理でき、チーム開発がスムーズになる
それでは、導入方法を見ていきましょう。
導入方法
公式ドキュメントに従ってインストールしていきます。
npx storybook@latest init
※対応フレームワークやバージョン要件については、公式ドキュメントをご確認ください。
起動
npm run storybook
でStorybookを起動できます。
http://localhost:6006/にアクセスすると、Storybookにデフォルトで用意されているサンプルのコンポーネント が表示されます。

コンポーネントに渡す引数に応じてボタンのサイズや色を指定できる、Buttonのコンポーネント
//stories\Button.tsx export interface ButtonProps { /** Is this the principal call to action on the page? */ primary?: boolean; /** What background color to use */ backgroundColor?: string; /** How large should the button be? */ size?: 'small' | 'medium' | 'large'; /** Button contents */ label: string; /** Optional click handler */ onClick?: () => void; } export const Button = ({ primary = false, size = 'medium', backgroundColor, label, ...props }: ButtonProps) => { return (コンポーネントの中身); };
ButtonコンポーネントのStoryファイル
コンポーネントに渡す引数によって、変わる見た目の変化を各Storyとして定義しています。
//stories\Button.stories.ts import type { Meta, StoryObj } from '@storybook/react'; import { fn } from '@storybook/test'; import { Button } from './Button'; const meta = { title: 'Example/Button', //Storybook上のサイドバーに表示される階層 component: Button, // 対象のコンポーネント parameters: { layout: 'centered', // Storybook上でのコンポーネントの表示位置 }, tags: ['autodocs'], //Docs(ドキュメント)を表示 argTypes: { backgroundColor: { control: 'color' }, //backgroundColor(string)プロパティをカラーピッカーで入力できるようにする }, args: { onClick: fn() }, } satisfies Meta<typeof Button>; export default meta; type Story = StoryObj<typeof meta>; // Buttonコンポーネントの各Storyを定義 export const Primary: Story = { args: { primary: true, label: 'Button', }, }; export const Secondary: Story = { args: { label: 'Button', }, }; ...他のストーリー
Storybookにコンポーネントを追加する
GUIで簡単に追加できるようになったので、今回は GUIでの追加方法 を紹介します!
追加する投稿フォームのコンポーネント
import React, { useState } from "react"; import { TextField, Button, Box } from "@mui/material"; type PostFormProps = { onAddPost: (message: string) => void; }; /** * 投稿フォームのコンポーネント */ export const PostForm = ({ onAddPost }: PostFormProps) => { const [message, setMessage] = useState(""); const maxLength = 150; const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { setMessage(e.target.value); }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (message.trim() && message.length <= maxLength) { onAddPost(message.trim()); setMessage(""); } }; return ( <Box component="form" onSubmit={handleSubmit} sx={{ display: "flex", flexDirection: "column", gap: 2, marginBottom: 2, }} > <TextField label="投稿内容" value={message} onChange={handleChange} variant="outlined" multiline minRows={3} fullWidth required helperText={ message.length > maxLength ? "150文字以内で入力してください" : `${message.length}/${maxLength}` } error={message.length > maxLength} /> <Button type="submit" variant="contained" color="primary" size="large" fullWidth disabled={!message.trim() || message.length > maxLength} > 投稿 </Button> </Box> ); };
今回は、features/
内にコンポーネントを追加するため、Storybookの設定ファイルにパスを指定します。
//storybook\main.ts import type { StorybookConfig } from "@storybook/nextjs"; const config: StorybookConfig = { stories: [ "../stories/**/*.mdx", "../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)", "../features/**/*.stories.@(js|jsx|mjs|ts|tsx)", //今回追加するディレクトリ ], ... }; export default config;
Storybookを起動すると、画面左上に「+」ボタンがあります。
「+」ボタンを押すと、コンポーネント選択のモーダルが表示されるので、 Storybookに追加したいコンポーネントを選択。
少し待つと...

Storybook用のファイルが自動生成され、コンポーネントが追加されます👏
生成されたファイル : features\Demo\PostForm.stories.tsx
import type { Meta, StoryObj } from '@storybook/react'; import { PostForm } from './PostForm'; const meta = { component: PostForm, } satisfies Meta<typeof PostForm>; export default meta; type Story = StoryObj<typeof meta>; export const Default: Story = {};
インタラクションテストを導入する
インタラクションテストとは、ボタンのクリックやテキスト入力などのユーザー操作を自動でシミュレーションできるテストです。
Storybookでは play関数を使うことで、ユーザーの操作を再現しながらコンポーネントの動作を確認し、期待通りに動作するかを検証できます。
今回は以下の2パターンを追加
- Default:テキスト入力 → 「投稿」ボタン押す一連の流れ
- 文字数上限:文字数が超過した時のボタン挙動
import type { Meta, StoryObj } from "@storybook/react"; import { PostForm } from "./PostForm"; import { expect, fn, userEvent, within } from "@storybook/test"; const meta = { component: PostForm, args: { onAddPost: fn(), // 関数をモック化 }, } satisfies Meta<typeof PostForm>; export default meta; type Story = StoryObj<typeof meta>; export const Default: Story = { play: async ({ canvasElement, args, step }) => { const canvas = within(canvasElement); const messageInput = canvas.getByRole("textbox", { name: "投稿内容" }); const submitButton = canvas.getByRole("button", { name: "投稿" }); await step("メッセージを入力", async () => { await userEvent.type(messageInput, "テストメッセージ", { delay: 50 }); }); await step("投稿ボタンを押下", async () => { await userEvent.click(submitButton); expect(args.onAddPost).toHaveBeenCalledWith("テストメッセージ"); // onAddPostが呼ばれたことを確認 expect(messageInput).toHaveValue(""); // メッセージがクリアされていることを確認 }); }, }; export const 文字数上限: Story = { play: async ({ canvasElement, step }) => { const canvas = within(canvasElement); const messageInput = canvas.getByRole("textbox", { name: "投稿内容", }); const submitButton = canvas.getByRole("button", { name: "投稿" }); await step("文字数上限を超えた場合", async () => { messageInput.focus(); await userEvent.paste("えんだ" + "あ".repeat(147)); }); await step( "エラーメッセージが表示され、ボタンが無効になっていること", async () => { expect(submitButton).toBeDisabled(); // 投稿ボタンが無効化されていることを確認 const errorMessage = canvas.getByText("150文字以内で入力してください"); expect(errorMessage).toBeInTheDocument(); // エラーメッセージが表示されることを確認 } ); }, };
実際の画面
ドキュメントページを作成する
Storybookにtags: ["autodocs"]
を追加するだけで、Docsページが自動で作成できます!
JSDocで補足なども書いておけば、その内容もDocsページに反映されるため、手間なくコンポーネントの仕様をドキュメント化できます。
/** * ## 投稿フォームコンポーネント * メッセージの入力欄と投稿ボタンが表示される * * - 未入力の場合は「投稿」ボタンは非活性 * - 150文字以上の場合は「投稿」ボタンは非活性 */ const meta = { component: PostForm, args: { onAddPost: fn(), }, tags: ["autodocs"], // ドキュメントページを追加 } satisfies Meta<typeof PostForm>; export default meta; type Story = StoryObj<typeof meta>; /** * 初期表示 */ export const Default: Story = {};
コンポーネント内のJSDocもDocsページに反映されます
type PostFormProps = { /** * 投稿ボタン押下時のコールバック関数 */ onAddPost: (message: string) => void; };
このように、StorybookのDocsページを活用すれば、
ドキュメント作成の手間を減らしながら、コンポーネントのStoryと仕様を一緒に管理することできます!
参考:Automatic documentation and Storybook
Storybookを使って感じたメリット
Storybookを使ってみて、特に良かった点を紹介します。
コンポーネント単体の状態をすぐに確認できる
Storybookを見れば自分が実装していないコンポーネントの仕様も把握しやすいため、キャッチアップやコンポーネントの再利用の判断などが進めやすくなりました。インタラクションテストでテスト内容を把握しやすい
ブラウザ上でユーザー操作をシミュレーションできるため、「どんな操作をテストしているのか?」が視覚的に分かりやすい。 (ステップ毎にUIの挙動を確認できるのも便利!)
まとめ
今回は基本的な導入方法を紹介しましたが、他にもCIに組み込んでテストを自動化したり、
ビジュアルテストやアクセシビリティテストもできたりと、さらに便利に活用することも可能です。
開発効率をさらに高めたい方は、ぜひ試してみてください!
この記事が、Storybook導入の参考になれば幸いです!
最後までお読みいただき、ありがとうございました! 😊
アドグローブでは、さまざまなポジションで一緒に働く仲間を募集しています!
詳細については下記からご確認ください。みなさまからのご応募お待ちしております。