Foundation
PackagesUIArchitecture

コンポーネントアーキテクチャ

3層構造 + Vendorsの全体像、共通原則、レイヤー間の依存ルール。

3層構造 + Vendors

コンポーネントは Primitives・Elements・Composites の3層で構成する。各層は明確に異なる責務を持ち、依存は常に上位層から下位層への一方向のみ許可する。

これに加えて、外部サービスのブランドに紐づくコンポーネントを格納する Vendors レイヤーが存在する。Vendorsは3層構造とは独立した軸に位置し、Primitivesに依存しない。

依存の方向:  Composites → Elements → Primitives
(上位層が下位層に依存する。逆方向の依存は禁止)

Vendors は3層から独立。Primitives・Elements・Composites に依存しない。
┌─────────────────────────────────────────────┐
│  Composites(複合コンポーネント)              │  ← 外部エクスポート
│  複数の Elements を組み合わせた完結したUI単位   │
├─────────────────────────────────────────────┤
│  Elements(単機能コンポーネント)               │  ← 外部エクスポート
│  Primitives を組み合わせた用途特化のコンポーネント│
├─────────────────────────────────────────────┤
│  Primitives(基盤コンポーネント)               │  ← 内部のみ
│  スタイル付きの最小構成単位                     │
└─────────────────────────────────────────────┘

┌─────────────────────────────────────────────┐
│  Vendors(外部ブランドコンポーネント)          │  ← 外部エクスポート
│  外部サービスのブランドに準拠したUI             │
└─────────────────────────────────────────────┘
役割判断基準エクスポート
Primitivesどう見えるか(what)を定義する色・サイズ・角丸・hover色などスタイルのバリエーション内部のみ
Elementsいつ・なぜそう見えるか(when / why)を決める状態に応じたvariant選択、Primitivesの組み合わせ外部公開
Composites複数のElementsを統合したUI単位を提供する子要素間の連携、開閉・フォーカス管理外部公開
Vendors外部ブランドに準拠したUIを提供する外部サービスのブランドガイドラインに縛られるか外部公開

共通原則

すべてのレイヤーに適用する設計原則を定義する。

Props設計

  • React.ComponentProps<"element"> を拡張し、variant・独自propsを追加する
  • Props名は対象が明確な名前にする。typevalue のような汎用名よりも、varianticonName のように意味が限定される名前を選ぶ
  • booleanのpropsは is / has で始める(例: isLoading, hasIcon
  • コールバックは on で始める(例: onClose, onChange
  • 選択肢がある場合、string ではなくユニオン型で限定する
// ❌ stringで受けると何でも渡せてしまう
interface BadProps {
  size: string;
}

// ✅ ユニオン型で選択肢を限定する
interface GoodProps {
  size: 'sm' | 'md' | 'lg';
}

スタイリング

  • CVA(class-variance-authority)でvariantを定義する
  • cn() でクラスを合成する
  • Tailwind CSSのユーティリティクラスのみを使用する。style 属性やarbitrary values(text-[13px])は禁止する
  • 色・スペーシング・角丸等はすべてデザイントークン経由で指定する
  • 外部エクスポートするコンポーネント(Elements / Composites)は className を受け付けない。利用者側のスタイル上書きを防ぎ、デザインの一貫性を保証する
  • Primitives層では className を受け取り cn() で内部クラスとマージする。これはデザインシステム内部での合成に限定される

スペーシング

コンポーネント内部の余白と外部の余白は、責務を持つ層が異なる。

種類責務
内部余白(padding, gap)Primitivesボタン内の px-4 py-2、アイコンとテキストの gap-2
外部余白(margin)コンポーネントは持たないレイアウトコンポーネント(Stack, Grid等)の gap で制御する

コンポーネントは自身の外側の余白を持たない。配置先のコンテキストによって適切な余白は変わるため、外部余白はレイアウトコンポーネントの gap で制御する。

// ❌ Primitiveが外部余白を持つ
const Button = ({ className, ...props }) => (
  <button className={cn('mb-4 px-4 py-2', className)} {...props} />
);

// ✅ 内部余白のみ。外部余白はレイアウトコンポーネントが担う
const Button = (props) => <button className="px-4 py-2" {...props} />;

// ✅ 利用者側ではレイアウトコンポーネントで間隔を制御する
<Stack gap="md">
  <Button />
  <Button />
</Stack>;

フィードバックの責務

ユーザー操作に対するフィードバックは、種類によって担う層が異なる。インタラクションデザインで定義された各フィードバックの実装責務を以下に示す。

フィードバック担う層実装方法
hover時の色変化PrimitivesCSS擬似クラス hover:
active / pressed時の色変化PrimitivesCSS擬似クラス active:
focus リングPrimitivesCSS擬似クラス focus-visible:
disabled の見た目(透明度等)PrimitivesCSS擬似クラス disabled: / aria-disabled:
loading → Spinner表示Elements条件分岐で Spinner を表示、disabled を渡す
error → 赤枠 + メッセージElementsaria-invalid を渡し、エラーメッセージを表示する
確認ダイアログ(破壊的操作)CompositesDialog で操作前に確認を挟む
Toast通知(成功・失敗)Composites操作完了後に Toast で結果を通知する

Primitivesは 見た目の変化 を定義し、Elements は いつその変化を起こすか を制御し、Composites は 操作の前後に挟むフロー を管理する。

data属性

すべてのコンポーネントに以下のdata属性を付与する。テスト・CSS セレクタ・デバッグに使用する。

属性用途
data-slotコンポーネントの識別data-slot="button"
data-variant現在のvariantdata-variant="default"
data-size現在のsizedata-size="md"

アクセシビリティ

  • WAI-ARIAパターンに準拠する。対応するARIAロールが存在する場合は必ず実装する
  • キーボード操作はコンポーネント内で完結させる
  • フォーカスリングは focus-visible で制御し、削除しない
  • 装飾的な要素(アイコン等)には aria-hidden="true" を付与する
  • 色だけで情報を伝えない。アイコン・テキスト等を併用する

命名規約

  • コンポーネント名はPascalCase(例: ActionButton
  • ファイル名はkebab-case(例: action-button.tsx
  • Props型はコンポーネント名 + Props(例: ActionButtonProps
  • CVA定義は コンポーネント名(小文字)Variants(例: buttonVariants
  • named exportのみ。default exportは禁止する

エクスポート規約

  • Primitivesは @bi-shop-it/ui から外部エクスポートしない
  • Elements / Composites / Vendors は @bi-shop-it/ui/<component-name> でフラットにエクスポートする
  • 型(Props型、ユニオン型等)は利用者が必要とする場合にのみエクスポートする

レイヤー間の依存ルール

許可する依存

Composites → Elements → Primitives
                      → ユーティリティ(cn, hooks)

Vendors → ユーティリティ(cn)のみ
  • Primitives は他のどの層にも依存しない(ユーティリティを除く)
  • Elements は Primitives とユーティリティにのみ依存する
  • Composites は Elements とユーティリティにのみ依存する
  • Vendors はユーティリティにのみ依存する。3層構造のどの層にも依存しない

禁止する依存

禁止パターン理由
Primitives → Elements上位層への逆依存は循環を生む
Primitives → Composites同上
Elements → Composites同上
Elements → Elements横方向の依存はレイヤーの境界を曖昧にする
Composites → Composites同上
Composites → Primitivesレイヤーの飛び越えは Elements の存在意義を失わせる
Vendors → PrimitivesVendorsは外部ブランドのスタイルを使うため、Primitivesのvariantに依存しない
Vendors → ElementsVendorsは3層構造から独立したレイヤーである
Vendors → Composites同上

例外

  • Icon は他のElementsから参照することを許可する。Iconはユーティリティに近い性質を持ち、他のElementsの内部で装飾として使用されるケースが多いためである

On this page