Skip to main content

デザインシステム コンポーネント設計ガイドライン

本ドキュメントは、チーム全体で一貫性のある高品質なUIコンポーネントを設計・実装するための基準を定めたものだ。Storybookを用いたコンポーネント駆動開発を前提とし、コンポーネントの分割、ディレクトリ構造、命名規則、依存関係のルールについて整理している。

1. コンポーネントの分割・分類基準(ディレクトリ設計)

コンポーネントは、「汎用性(どこでも使えるか)」と「ドメイン依存(特定のデータモデルに依存するか)」 を軸に分割し、依存関係を一方向(上から下)に保つ。

1-1. 推奨されるディレクトリ構成の概念 (Feature-Sliced Designのマッピング)

デザインシステムの概念である「汎用性」と「ドメイン依存」を、プロジェクトのディレクトリ構成(Bulletproof Reactベース)にマッピングして管理する。

src/
├── components/ # UI Kit: 汎用コンポーネント (Button, Input, Avatar など)
│ # ※ アプリケーション全体で使い回せる「ui」層に相当する
├── layouts/ # Layouts: アプリ全体で共有する専用ウィジェット (GlobalHeader など)
└── features/
└── [feature]/
└── components/ # Domain/Widgets: 特定の機能に特化したコンポーネント群
# ※ 物理的には同階層でも、ドメイン部品(domain)と組み合わせブロック(widgets)を概念的に意識して設計する
  • 依存のルール: 上位層(widgets > domain > ui)は下位層を利用できるが、下位層(ui)は上位層に依存してはならない。
  • 構築戦略: 複雑なUIはゼロから自作せず、Radix UI等のHeadless UIを土台とし自社のスタイルを被せる手法を推奨する。

1-2. 汎用コンポーネント (UI Kit) src/components/

  • 特定のビジネスロジックやデータモデルに依存せず、別プロジェクトでも転用可能なレベルの抽象度を持つ。
  • プロパティごとに個別の値を受け取る設計にする(例: User オブジェクトを丸ごと渡すのではなく、name, imageUrl として渡す)。
  • アプリケーション全体で共有するため、src/components/ 直下に配置する。
  • 構成の例: コンポーネントの「役割」等での階層化は避け、フラット(平坦) に直列で配置する。
    src/components/
    ├── Button/ # コンポーネント単位でディレクトリを分けるか、もしくは Button.tsx のように配置
    ├── IconButton/ # ※Buttonの内部に作らず、同じ階層にフラットに配置する
    ├── TextField/
    ├── Dialog/
    ├── Heading/
    ├── Alert/
    └── Tabs/

1-3. ドメインコンポーネント (Domain) src/features/[feature]/components/

  • バックエンドのデータモデルや特定の機能に紐づくコンポーネント。
  • APIスキーマやデータモデルの構造(ObjectやArray)をそのままプロパティとして受け取ってよい。
  • features/[feature]/components/ 内において、画面全体を構成するブロック(Widgets)を組み立てるための「意味を持った部品」として機能する。
  • 共通化の罠 (Rule of Three): 見た目が似ているからといってすぐに共通化しない。同じパターンが3回登場したら共通化を検討する。
  • 構成の例: アプリケーションの機能(Feature)ごとに分類して配置する。これにより、関連するロジック(hooks等)と同じ凝集度で管理できる。
    features/
    ├── user/ # user関連の機能ドメイン
    │ └── components/
    │ └── UserCard/ # UserList, UserAvatar などユーザー関連
    ├── product/ # product関連の機能ドメイン
    │ └── components/
    │ └── ProductListItem/
    └── auth/
    └── components/
    └── LoginForm/

1-4. ウィジェット (Widgets) src/features/[feature]/components/ もしくは src/layouts/

  • 汎用コンポーネントとドメインコンポーネントを組み合わせ、独立して機能する画面の大きなブロック(セクション)を構成する。
  • ページコンポーネントは、このWidgetsを並べるだけで完成する状態が理想だ。
  • features/[feature]/components/ 内に置かれる場合、ドメイン(Domain)コンポーネントとは物理的には同階層になる。これらは命名規則(例:*Section, *Block)や設計者の意識によって「これは独立した大きなブロックである」と概念的に区別する。
  • 構成の例: 画面上の「大きな機能ブロック」ごとに分類する。特定の機能に依存する場合は features/ に配置する。アプリケーション全体で共有するレイアウトの枠組み(ヘッダー等)は、汎用UI(src/components/)と区別するため src/layouts/ などに配置する。

    src/layouts/
    ├── GlobalHeader/ # ロゴ、ナビゲーション、ユーザーメニューを含む全体のブロック
    └── GlobalFooter/ # アプリケーション共通のフッター

    features/
    └── report/
    └── components/
    └── ReportDashboardSidebar/ # 特定の機能における複雑なブロック(概念的なWidgets層として実装)

2. 命名とプロパティ (Props) の設計

2-1. コンポーネントの命名

  • 業界標準に合わせる: DialogPopover など、既存の語彙に揃える。
  • 状態・バリアントを名前に含めない: LoadingButton のような命名は避け、Button + isLoading プロパティで表現する。
  • レイアウトバリエーションの接尾辞: 形状で名前のルールを統一する(*Card : リッチな縦型、 *Cell : 一覧用の横幅いっぱい、 *Item : 最小の行表示)。

2-2. プロパティの型と直交性

  • 直交性: プロパティ同士は独立させる。例: style="ghost"style="danger"を混ぜず意味と強調度に分ける。
  • 必須と任意: 必須Propsを最小限にし、よく使われる値をデフォルト(例:size="medium")に設定する。
  • Boolean型(真偽値): isDisabled, hasIcon, showDivider などポジティブな命名にする。もし排他的に3つ以上増えそうなら早めに Enum型(列挙型) position="left"|"right"|"none" へ昇格させる。

2-3. バリアント爆発を防ぐ Slot (Element) パターン

  • hasLeftIcon のようなBooleanを量産するのではなく、**「差し込み口(Slot)」**を用意しReactNode等を受け取る設計にする。
  • プロパティが10個以上に膨らんできた場合は、設定値(Configuration)ではなく、スロット(Composition)に切り替えるサイン。

3. コンポーネントの責任範囲とレイアウト

コンポーネントは「自分の中身だけに責任を持ち、どこに置くかは親要素の責務」とするカプセル化を徹底する。

3-1. 余白 (Margin / Padding)

  • 原則: コンポーネント自体に外側の余白(margin)を持たせない
  • 内側の余白(padding)はコンポーネントの責任。隣接要素との間隔は、配置する親(Flexbox要素などの gap)が制御する。

3-2. 幅の振る舞い (Width) と オーバーフロー

  • コンポーネントのデフォルト幅を明確にする。極端な崩れを防ぐため min-widthmax-width を設定する。
  • テキストのオーバーフロー: text-overflow: ellipsis(省略)、line-clamp(行数制限で折り返す)などを仕様として明記する。

3-3. アセット(アイコン等)の管理

  • アイコンは画像(PNG等)でなく、SVGまたはアイコンライブラリ(Reactコンポーネント) として実装する。
  • 色の自動追従: ダークモード等に対応するため、SVGの塗りは #1A1A1A などの固定値にせず、currentColor を使用し親のテキスト色に自動で一致させる。

4. UIスタック(状態の網羅)と運用

4-1. 5つのUIスタック

StorybookのStories作成時には、データが理想的に入ってくる状態だけでなく、現実的な各状態を網羅する。

  1. Ideal State: 全データが揃っている理想状態。
  2. Empty State: データ0件時の案内やプレースホルダー。
  3. Loading State: スケルトンスクリーンやスピナー表示。
  4. Partial State: データが部分欠損している状態のフォールバック(例: 画像URLがない場合はイニシャル表示)。
  5. Error State: データ取得失敗時のフォールバック。

4-2. 変更の安全性とライフサイクル

  • 追加は安全、変更は危険: 新しいバリアントの追加は安全(後方互換性あり)だが、既存のProps名変更や削除は破壊的変更となり修正コストが高い。
  • 廃止設計: 古いコンポーネントを消す際は、[Deprecated]@deprecated で警告を出して移行を促し、使われなくなった後に削除する。