ui-component-architecture について
共有 UI パッケージを使うチーム向けのコーディングスキルです。既存プリミティブの再利用と適切な表示コンポーネントの抽出を促し、画面を読みやすく保ちます。
主な機能
- 01共有 UI のエクスポートを確認・再利用
- 02ルートをコンポーネント構成に集中
- 03共有コンポーネントの境界を設定
- 04テーマトークンとバリアントを使用
- 05パッケージ境界と動作を検証
活用シーン
- 01共有 UI パッケージのあるアプリで画面を構築
- 02インライン記述の多い画面をリファクタリング
- 03再利用可能な UI プリミティブを追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ darkmatter/ skills --skill ui-component-architectureインストール後、Claude Code で/ui-component-architectureを入力して読み込まれたか確認します。
同梱ファイルreference/agents-md-rule.mdreference/eslint-guardrails.md
詳細
UI コンポーネントアーキテクチャ
このガイドが改善するのは、エージェントが数百行もの <div className="..."> による Tailwind の寄せ集めとして画面を作り、共有 UI パッケージにすでに存在する Button、Card、Badge を再実装し、本当に再利用可能な要素をそのパッケージに戻すこともない、という失敗パターンです。その結果、スタイルが重複し、ビジュアル表現に一貫性がなくなり、誰も読めない画面ができあがります。
解決策は、作る前に再利用すること、そして再利用可能な単位を共有 UI パッケージに切り出して、画面を薄く保つことの 2 つの習慣です。ただし、一度限りのレイアウト用の接着コードまで過剰に切り出してはいけません。このパッケージは独立したパッケージでなければなりません。パスとインポートエイリアスはリポジトリごとに異なります(ADR-0013)。ワークスペース(package.json の workspaces、packages/*、ベンダーツリー)を調べて見つけてください。必須の名前を勝手に決めないでください。
コンポーネントの境界を決める際は、codebase-design を適用してください。1 つの機能を説明するために必要な場合は、状態、ハンドラー、非公開コンポーネント、レイアウトをまとめておきます。切り出しによって動作を隠せる、または有用な再利用が可能になるべきです。JSX の長さや呼び出し箇所が 2 つあることだけでは、切り出しの根拠になりません。
使用する場面
- 共有 UI パッケージのあるアプリで、新しいページ/ルート/画面を作成する場合。
- インラインの div が多い既存の画面を再スタイリングまたはリファクタリングする場合。
- 再利用可能なプリミティブ(ボタン、カード、バッジ、入力、ダイアログ、空の状態、統計、スケルトン、アバター)のように見えるコンポーネントを作成する場合。
- ユーザーが「X ページを作って」「Y ビューを追加して」「Z コンポーネントを作成して」と依頼した場合。
使用しない場面
- React 以外のコード、または再利用先となる共有 UI パッケージがない単一パッケージのアプリ。
- 独立した責務がなく、切り出す理由のない一度限りのレイアウト用ラッパー。
- 新しい表示要素を追加するのではなく、既存のコンポーネント内部を編集する場合。
原則
作る前に再利用する
JSX を書く前に、すでにあるものを調べて、同じものを一から作らないようにします。
- 共有 UI パッケージ — バレルファイルまたは
package.jsonのexportsを確認します。必要な要素を grep で検索します。 - 外部レジストリ — 共有パッケージにまだないブロックについては、
shadcn-registry-firstスキルを参照してください。流れは、レジストリ → アプリ画面 → 安定した要素を UI パッケージに昇格させる、です。
すでに存在する Button、Card、Badge、Input、Dialog、EmptyState を再実装しないでください。
画面を薄く保つ
ルートでは、データと主要な視覚的機能を追いやすくします。既存のプリミティブを組み合わせ、意味のあるインターフェースを持つコンポーネントを切り出します。まとまりのある状態とイベント処理は、それらが制御する UI と一緒に保ちます。画面の props とマークアップを転送するだけのコンポーネントは、複雑さを隠さず、コードをたどる手間を増やします。非公開コンポーネントは、それを所有するコンポーネントと同じファイルに置いてもかまいません。
再利用可能な単位を UI パッケージに昇格させる
共有パッケージに移すかどうかを決める、重要な判断基準は次のとおりです。
- 昇格させるもの: 独立したインターフェースを持つ再利用可能な表示単位、または自己完結した視覚的プリミティブ —
Button、Card、Badge、Avatar、EmptyState、Stat、Skeleton、Input、Dialog。2 か所目で再利用されている場合は調査する根拠になりますが、見た目が似ているだけでは同一の単位とは判断できません。 - アプリ内に残すもの: アプリ固有の構成(特定のダッシュボードのレイアウトなど)、一度限りの接着コード、データの配線やビジネスロジックを含むもの。
- 判断に迷ったら、ローカルに残します。 1 か所でしか使わないコンポーネントでも、相当量の動作を隠している場合がありますが、アプリ固有の props はアプリに属します。2 か所目での利用は境界を見直すきっかけであり、アプリの状態を共有パッケージに移す理由ではありません。
散在するマジック値ではなく、トークンとバリアントでスタイルを指定する
- 生の 16 進数リテラルを
classNameに散在させず、テーマトークン(CSS 変数、Tailwind のテーマ)を使います。 - バリアントを持つコンポーネントでは、条件付きクラス文字列の寄せ集めではなく、
cnとバリアントマップ(またはcva)を使います。 - 再利用可能な視覚的単位のスタイルは一元化し、付随的なレイアウトはローカルに保ちます。
ワークフロー
1. 共有 UI パッケージの内容を確認する
ワークスペースの一覧、packages/*、ベンダーツリーからパッケージを見つけます。すでにエクスポートされているものを一覧にします。再実装せずに再利用できるプリミティブを確認します。
2. 画面をコンポーネント構成としてスケッチする
詳細を書く前に、名前付きコンポーネントのツリーとしてページの構成をまとめます。各ノードに、UI パッケージに存在する/アプリ固有の構成/新規だが再利用できそう のいずれかを付けます。これにより、インポートするもの、インラインで記述するもの、切り出すものがわかります。
3. 画面は薄く作る
既存のプリミティブを組み合わせ、各インタラクションを 1 か所で理解できるようにします。ファイルが設定済みの上限を超える場合は、適切な責務で分割するか、範囲を絞って上限の引き上げを記録します。ファイルの行数だけでコンポーネントを選んではいけません。
4. 再利用可能なパーツを切り出す
次の卒業基準を満たすパーツは、それぞれ以下のようにします。
- UI パッケージの兄弟コンポーネントの近くに配置します。
- バレルまたは
package.jsonのexportsからエクスポートします。 - props は 汎用的 に保ちます。アプリ固有の型、ルート、ストア、API クライアントを UI プリミティブに漏らしてはいけません。共有 UI コンポーネントは、データを props で受け取る、状態を持たない表示用コンポーネントにします。
- バリアントは
cn/cvaと一緒に配置します。
5. 検証する
- 画面ではプリミティブを再定義せず、UI パッケージからインポートしています。
- UI パッケージ内のコンポーネントに、アプリ固有のインポート(
@/...、アプリのルーター、ストア、API クライアント)がありません。 - リポジトリのコマンドで型チェックとビルドが通ります。
- インタラクションはコンポーネントの公開インターフェースを通じて完結します。テストでは、非公開のフックやヘルパーの呼び出しではなく、ユーザーの振る舞いを検証します。
切り出すものと、アプリに残すもの
| アプリに残すもの | UI パッケージに切り出すもの |
|---|---|
/dashboard ページのレイアウト | StatCard、Badge、EmptyState |
| ルート固有のデータ取得と接続 | Button、Input、Dialog、Skeleton |
| 1 つのマーケティングページで一度だけ使うヒーロー | 3 ページ以上で再利用するヒーローのバリアント |
| ビジネスロジック、機能フラグ、アプリの状態 | props を受け取る表示用プリミティブ |
過剰な切り出しを避ける
過剰な切り出しは逆の失敗であり、同じくらい大きなコストがかかります。切り出しを急ぎすぎている兆候は次のとおりです。
- props の名前が特定の画面にちなんだ UI パッケージのコンポーネント。
- 長い props のリストを渡すだけで、インタラクションや表示上の判断を隠していないコンポーネント。
- アプリのストア、ルーター、API クライアントをインポートする UI プリミティブ。
対処法:まとまりのあるアプリコンポーネントにアプリ固有の振る舞いを保ちます。使いやすい表示用インターフェースを備えたときに限り、共有プリミティブを切り出します。一度しか使わない場合でも、ローカルヘルパーや非公開コンポーネントは近くに保ちます。
参考資料
reference/agents-md-rule.md— 利用側リポジトリのAGENTS.mdに貼り付けられる、常時適用の短いルールです。最も効果の高い手段です。スキルは必要に応じて読み込まれるため、「すべての UI 編集」で適用されないことがありますが、AGENTS.mdのルールは常にコンテキストに含まれます。共有 UI パッケージを持つ各リポジトリに追加し、そのリポジトリで実際に使われているパッケージパスとインポートエイリアスを記入してください。reference/eslint-guardrails.md— 境界を機械的に強制する lint ルールです(アプリの JSX での生の 16 進数の使用禁止、JSX の深さの上限、そして最も重要な、UI パッケージ内のアプリのインポートを禁止する境界ルール)。
他のスキルとの関係
shadcn-registry-first— レジストリから 外部 ブロックを取り込みます。このスキルでは、内部 での再利用と切り出しを扱います。併用できます。レジストリ → アプリ画面 → 安定したパーツを UI パッケージに切り出す、という流れです。darkmatter-design-system— アプリがそのシステムを使う場合のビジュアル言語とトークンを扱います。それらのトークンは引き続き UI パッケージに置きます。このスキルではパッケージの境界を扱います。repository-organization— ディレクトリとパッケージの配置場所を扱います。このスキルでは、さらに一段下のコンポーネント再利用の粒度を扱います。vercel-react-best-practices— 作成して組み合わせるコンポーネントのパフォーマンスパターンを扱います。