フロントエンド開発の現場では、画面全体を一度に組み上げるのではなく、ボタンやフォームといったUI部品を先に設計・実装し、それらを組み合わせてページを完成させるアプローチが主流になっています。この手法が コンポーネント駆動開発(CDD: Component-Driven Development) です。
CDDはUIの再利用性・テスト容易性・デザインの一貫性を同時に高められる設計手法ですが、ツール選定や粒度設計で迷うケースも少なくありません。ここでは、CDDの基本原則からツール比較、フレームワーク別の実装手順、そして導入時に陥りやすい失敗パターンまでを実務の観点で整理します。
コンポーネント駆動開発(CDD)の定義と基本原則
CDDはボトムアップ方式のUI構築手法です。従来のページ単位のトップダウン開発と異なり、最小のUI部品(コンポーネント)から設計を始め、それらを段階的に組み合わせて画面全体を構成します。
ボトムアップ設計の流れ
CDDにおける設計フローは次の4段階で進みます。
- 最小部品の設計: ボタン、テキスト入力欄、アイコンなど、これ以上分割できない要素を定義
- 複合部品の組み立て: 最小部品を組み合わせて検索バーやカードUIなどの中規模コンポーネントを構築
- セクション単位の統合: 複合部品をまとめてヘッダー、サイドバー、フッターなどのページセクションを形成
- ページの完成: セクションを配置し、ルーティングやデータ取得ロジックを接続
この流れにより、各コンポーネントは単体で動作確認やテストが可能な状態を保てます。
CDDが解決する3つの課題
| 課題 | 従来手法での問題 | CDDによる解決 |
|---|---|---|
| UI の不整合 | ページごとにボタンの見た目や挙動が異なる | 共通コンポーネントを使い回すため一貫性を維持 |
| テスト困難 | ページ全体を起動しないとUIを確認できない | コンポーネント単位で独立してテスト可能 |
| デザイン変更の影響範囲 | 修正箇所が散在して把握しにくい | コンポーネント1箇所を修正すれば全ページに反映 |
Atomic Designとの関係 — そして「その先」の分類手法
CDDにおけるコンポーネント分類の定番として知られるのが、Brad Frost氏が2013年に提唱したAtomic Designです。化学の概念をUIに当てはめ、5段階の階層を定義しています。
| 階層 | 名称 | 具体例 |
|---|---|---|
| 第1層 | Atoms(原子) | ボタン、ラベル、アイコン |
| 第2層 | Molecules(分子) | 検索フォーム(入力欄 + ボタン) |
| 第3層 | Organisms(生体) | ヘッダー(ロゴ + ナビ + 検索フォーム) |
| 第4層 | Templates | ページレイアウトの骨格 |
| 第5層 | Pages | 実データが入った最終画面 |
「Atomic Designは古い」と言われる背景
検索トレンドを見ると「アトミックデザイン 古い」「アトミックデザイン 代替」といったキーワードの検索需要が増えています。批判の主なポイントは以下のとおりです。
- 分類の曖昧さ: MoleculesとOrganismsの境界が開発者によって異なり、チーム内で認識がずれやすい
- 過剰な階層: 5階層すべてを律儀に適用すると、ディレクトリ構造が深くなり管理コストが増大する
- 機能ベースとの相性: ドメインや機能単位でディレクトリを分けるアーキテクチャ(Feature-Slicedなど)と噛み合わない場面がある
Atomic Designに代わる分類アプローチ
実務ではAtomic Designをそのまま採用するのではなく、プロジェクトの規模に合わせてシンプル化した分類が増えています。
| アプローチ | 階層構成 | 適したプロジェクト規模 |
|---|---|---|
| 3層分類 | Primitives / Composites / Views | 小〜中規模 |
| Feature-Sliced Design | shared / entities / features / widgets / pages | 中〜大規模 |
| 用途別分類 | ui / form / layout / page | 小規模・プロトタイプ |
いずれの分類でも、CDDの根幹である「小さな部品から組み上げる」という原則は変わりません。分類ルールはあくまで整理手段であり、チームで合意できる粒度であれば十分です。
CDDを支えるツール比較 — Storybook・Ladle・Histoire
コンポーネントを単体で表示・操作・テストするための「カタログツール」はCDDの実践に欠かせません。2026年時点で選択肢となる主要ツールを比較します。
| 項目 | Storybook | Ladle | Histoire |
|---|---|---|---|
| 対応フレームワーク | React, Vue, Angular, Svelte, Web Components | React のみ | Vue, Svelte |
| バンドラー | Webpack / Vite | Vite 固定 | Vite 固定 |
| 起動速度 | やや遅い(Webpack時) / Viteで改善 | 高速 | 高速 |
| アドオン・拡張性 | 非常に豊富(300以上の公式・コミュニティアドオン) | 最小限 | Vue向け機能が充実 |
| Visual Regression Testing | Chromatic連携、test-runner公式サポート | 外部ツールで対応 | 外部ツールで対応 |
| ドキュメント生成 | Autodocs / MDX | 基本機能 | 基本機能 |
| 学習コスト | 中(設定項目が多い) | 低(設定不要に近い) | 低(Vue開発者向け) |
| 適したケース | マルチフレームワーク・大規模チーム | React単体・高速起動重視 | Vue 3プロジェクト |
ツール選定の判断基準
- フレームワークが単一でない → Storybook一択。Angular・Svelteも含めて統一管理できる唯一の選択肢です
- Reactプロジェクトで起動速度を重視 → Ladleが有力。Viteベースで設定ファイルもほぼ不要なため、小規模プロジェクトとの相性が良好です
- Vue 3プロジェクト → Histoireが最も自然なAPI体験を提供します。Vue単体ならStorybookより設定が軽量です
React + TypeScript でのCDD実装手順
ここからは、React + TypeScript + Storybook の組み合わせで具体的な実装手順を示します。
ディレクトリ構成
src/
├── components/
│ ├── ui/ # 汎用UIコンポーネント
│ │ ├── Button/
│ │ │ ├── Button.tsx
│ │ │ ├── Button.stories.tsx
│ │ │ └── Button.test.tsx
│ │ └── Input/
│ │ ├── Input.tsx
│ │ ├── Input.stories.tsx
│ │ └── Input.test.tsx
│ ├── form/ # フォーム関連コンポーネント
│ │ └── ContactForm/
│ │ ├── ContactForm.tsx
│ │ ├── ContactForm.stories.tsx
│ │ └── ContactForm.test.tsx
│ └── layout/ # レイアウトコンポーネント
│ └── Header/
│ ├── Header.tsx
│ └── Header.stories.tsx
├── tokens/ # デザイントークン
│ └── colors.ts
└── pages/
└── Home.tsx
この構成のポイントは、各コンポーネントフォルダにソース・ストーリー・テストを同居させることです。関連ファイルが離れた場所に散らばると、コンポーネントの追加・修正時に複数ディレクトリを行き来する手間が発生します。
Buttonコンポーネントの実装
// src/components/ui/Button/Button.tsx
import { type ComponentPropsWithoutRef } from 'react';
type Variant = 'primary' | 'secondary' | 'danger';
type Size = 'sm' | 'md' | 'lg';
type ButtonProps = ComponentPropsWithoutRef<'button'> & {
variant?: Variant;
size?: Size;
};
const variantStyles: Record<Variant, string> = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
danger: 'bg-red-600 text-white hover:bg-red-700',
};
const sizeStyles: Record<Size, string> = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
};
export function Button({
variant = 'primary',
size = 'md',
className = '',
children,
...props
}: ButtonProps) {
return (
<button
className={`rounded font-medium transition-colors
${variantStyles[variant]} ${sizeStyles[size]} ${className}`}
{...props}
>
{children}
</button>
);
}
従来のCSSクラス名を文字列で直接書く方法と比較すると、TypeScriptの型でvariantとsizeの値を制約しているため、存在しないバリエーションの指定がコンパイル時に検出されます。
Storybook Story の作成
// src/components/ui/Button/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta = {
title: 'UI/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'danger'],
},
size: {
control: 'select',
options: ['sm', 'md', 'lg'],
},
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: { children: '送信する', variant: 'primary', size: 'md' },
};
export const Secondary: Story = {
args: { children: 'キャンセル', variant: 'secondary', size: 'md' },
};
export const Danger: Story = {
args: { children: '削除', variant: 'danger', size: 'sm' },
};
export const AllSizes: Story = {
render: () => (
<div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
</div>
),
};
tags: ['autodocs'] を指定すると、Propsの一覧と説明が自動生成されます。手作業でドキュメントを書かなくても、コンポーネントの使い方を他の開発者やデザイナーが即座に把握できます。
Vue 3 / Svelteでの導入ポイント
CDDの原則はフレームワークに依存しません。Vue 3やSvelteでも同じボトムアップの考え方を適用できます。
Vue 3 + Histoire の場合
<!-- src/components/ui/BaseButton.vue -->
<script setup lang="ts">
defineProps<{
variant?: 'primary' | 'secondary' | 'danger'
size?: 'sm' | 'md' | 'lg'
}>()
</script>
<template>
<button
:class="[
'base-button',
`base-button--${variant ?? 'primary'}`,
`base-button--${size ?? 'md'}`
]"
>
<slot />
</button>
</template>
Histoireのストーリーファイルは .story.vue という拡張子で、Vue SFC(Single File Component)と同じ記法で記述できます。StorybookのCSF形式に慣れていないVue開発者にとっては、学習コストの低さが大きな利点です。
Svelte + Storybook の場合
Svelte 5のRunes構文には Storybook 9(2025年7月リリース)から正式対応しています。$props() でプロパティを受け取るスタイルがそのまま Storybook のArgsと連携するため、React版とほぼ同じ開発体験が得られます。
CDDにおけるテスト戦略
CDDの大きな利点のひとつが、コンポーネント単位での段階的なテストが可能になる点です。
テストの3層構造
| レイヤー | 対象 | ツール例 | 確認内容 |
|---|---|---|---|
| ユニットテスト | 個別コンポーネントのロジック | Vitest + Testing Library | Props に応じた出力、イベント発火 |
| Visual Regression | 見た目の変化検知 | Chromatic / Percy / reg-suit | スクリーンショットのピクセル差分 |
| アクセシビリティ | WCAG準拠チェック | axe-core / Storybook a11y addon | コントラスト比、ARIA属性、キーボード操作 |
Visual Regression Testing の実践
Visual Regression Testing(VRT)は、コンポーネントの見た目が意図せず変わっていないかを画像差分で自動検出する手法です。
Storybookと組み合わせる場合、代表的な選択肢は以下の2つです。
- Chromatic: Storybook開発元が提供するクラウドサービス。ストーリーごとにスクリーンショットを自動取得し、PR単位で差分をレビューできます。無料プランでは月5,000スナップショットまで利用可能です
- reg-suit + Storycap: OSSの組み合わせ。CIパイプライン上でスクリーンショットを取得し、S3やGCSに保存した基準画像と比較します。クラウドサービスへの依存を避けたい場合に有効です
アクセシビリティテストの自動化
Storybookの @storybook/addon-a11y アドオンは、各ストーリーの表示時にaxe-coreを実行してWCAG違反を検出します。コントラスト不足やalt属性の欠落をコンポーネント開発の段階で発見できるため、ページ統合後に問題が噴出するリスクを低減できます。
デザイナーとの協業 — デザインシステムとCDDの接続
CDDはエンジニアだけの手法ではなく、デザイナーとの共同作業を円滑にする仕組みでもあります。
デザイントークンの共有
デザイントークンとは、色・フォントサイズ・余白・角丸などのデザイン上の定数を変数として定義したものです。FigmaのVariables機能で定義したトークンを、Style Dictionaryなどのツールでコード側に同期することで、デザインと実装の値のずれを防止できます。
// tokens/colors.ts — Style Dictionaryの出力例
export const colors = {
primary: '#2563EB',
primaryHover: '#1D4ED8',
secondary: '#E5E7EB',
danger: '#DC2626',
text: '#1F2937',
textMuted: '#6B7280',
} as const;
Figmaとコードの連携フロー
- デザイナーがFigma上でVariables(色・タイポグラフィ・スペーシング)を定義
- Figma Tokens PluginやTokens Studio for FigmaでJSON形式にエクスポート
- Style DictionaryでCSS変数またはTypeScript定数に変換
- コンポーネントはトークン経由で値を参照し、直接的な色コード指定を排除
この連携により、デザイナーがFigma上でブランドカラーを変更すると、CIパイプラインを通じてコード側のトークン定義も自動更新され、Storybook上で即座に反映を確認できます。
コンポーネントの粒度をデザイナーと合わせるコツ
CDDにおいて頻出する摩擦は「何をひとつのコンポーネントと見なすか」の認識ずれです。以下のルールをチーム内で事前に合意しておくと、手戻りを減らせます。
- Figmaのコンポーネント = コードのコンポーネント を原則とする。デザイン側でパーツ化されていないものをコード側で勝手に分割しない
- バリエーション(色違い・サイズ違い)は1コンポーネントのPropsで表現し、別コンポーネントにしない
- 「このコンポーネントは3箇所以上で使うか?」を判断基準にし、1箇所でしか使わないものは無理にコンポーネント化しない
CDD導入で陥りやすい失敗パターンと対策
失敗1: 過度な共通化
「再利用性が高い=良い設計」と考えすぎて、あらゆるUIを共通コンポーネントに抽出してしまうケースです。共通化したコンポーネントに各画面固有の分岐が入り込み、Props が肥大化して保守が困難になります。
対策: 共通化は3箇所以上で同じパターンが出現してから検討します。事前の予測で共通化するのではなく、実際の重複を確認してから抽出する「事後抽象化」の姿勢が重要です。
失敗2: Storybookが放置される
導入当初はストーリーを書いていたものの、徐々にメンテナンスされなくなり、壊れたストーリーが放置される状態です。
対策: CIパイプラインにStorybookのビルドチェックを組み込みます。storybook build がエラーなく完了することをPRマージの条件にすれば、壊れたストーリーの放置を防止できます。加えて、VRTを導入すると見た目の変更も検知されるため、ストーリーのメンテナンスが自然とワークフローに組み込まれます。
失敗3: デザインとコードの乖離
Figma上のデザインとStorybook上のコンポーネントが徐々にずれていき、どちらが正しいのか分からなくなるパターンです。
対策: デザイントークンの自動同期(前述のFigma → Style Dictionary → コード)を導入し、単一の情報源(Single Source of Truth)を確立します。トークン経由で値を管理すれば、デザインファイルとコードの乖離は構造的に発生しにくくなります。
CDDの導入ステップ
既存プロジェクトにCDDを段階的に導入する際の推奨ステップを示します。
- カタログツールの導入: 既存プロジェクトにStorybookまたは代替ツールをインストールし、まず1つのコンポーネントのストーリーを作成
- デザイントークンの定義: 色・フォント・スペーシングの定数を抽出し、コンポーネントからの直接値指定を排除
- 新規コンポーネントからCDDを適用: 既存コードのリファクタリングは後回しにし、新しく作るUIからCDDフローを実践
- CIへの統合: Storybookビルドチェック・VRT・a11yテストをCIに追加
- 既存コンポーネントの段階的移行: 修正やリファクタリングのタイミングで既存UIをCDD対応に移行
全てを一度に切り替える必要はありません。新規開発から適用を始め、チームがCDDのワークフローに慣れた段階で既存コードの移行に着手するのが、もっとも摩擦の少ない導入パターンです。
まとめ
コンポーネント駆動開発(CDD)は、UIを最小部品から組み上げるボトムアップ手法です。StorybookなどのカタログツールとVisual Regression Testingを組み合わせることで、見た目の一貫性・テスト容易性・デザイナーとの協業効率を同時に向上させられます。
Atomic Designの5層分類にこだわる必要はなく、チームの規模やプロジェクト特性に応じた分類ルールで運用するのが現実的です。導入時は「共通化しすぎない」「Storybookを放置しない」「デザインとコードの乖離を防ぐ」の3点を意識すると、CDD特有の落とし穴を避けられます。