shadcn/ui をどう扱うか ― 「使う」から「取り込む」への設計判断
Published on 2026年2月15日
アーキテクチャはじめに
shadcn/ui は、従来の npm コンポーネントライブラリとは異なるコンポーネントコレクションです。 インポートして使うのではなく、CLI を通じてあらかじめ書かれたコンポーネントのソースコードをプロジェクト内にコピーする仕組みになっています。
この特性が、ある設計上の問いを生みます。
生成されたコードを「shadcn のコンポーネント」として扱い続けるのか、それとも「自分たちのコンポーネント」として取り込むのか。
本記事では、Atomic Design を採用したプロジェクトにおいて shadcn/ui をどう位置づけたか、その判断過程を整理します。
前提:Atomic Design と atoms 層の役割
本プロジェクトでは、UIコンポーネントの設計手法として Atomic Design を採用しています。
Pages
└── Templates(レイアウト)
└── Organisms(プロダクト固有)
└── Molecules(汎用・組み合わせ)
└── Atoms(最小単位)
このうち atoms は、UIを構成する最小単位のパーツです。ボタン、入力フィールド、カードといった、これ以上分解できない基本要素がここに属します。
atoms 層の責務は以下の3つに集約されます。
| 責務 | 内容 |
|---|---|
| スタイルの定義と保護 | cva でバリアントを定義し、見た目をロック |
| headless UI のラップ | Radix UI 等を内部実装として使用 |
| 契約の提供 | 外部に公開する props のインターフェース |
この atoms 層に shadcn/ui のコードをどう配置するかが、本記事のテーマです。
shadcn/ui の正体
shadcn/ui のコンポーネントは、分解すると3つの要素で構成されています。
| 要素 | 役割 |
|---|---|
| Radix UI | headless UI(振る舞い・アクセシビリティ) |
| cva(class-variance-authority) | バリアント管理 |
| Tailwind CSS | スタイリング |
つまり shadcn/ui とは、Radix UI の上にスタイル定義を載せた 初期コードの供給元 にすぎません。 コードを取り込んだ時点で、shadcn との関係は実質的に切れています。
2つの捉え方
shadcn/ui のコードをプロジェクトに取り込んだとき、設計上の捉え方は2つに分かれます。
1. shadcn をそのまま使う
- atoms は常に shadcn 由来で構成される
- shadcn のエコシステム(CLI・ドキュメント・コミュニティ)を参照先として活用し続ける
- スタイルのカスタムは最小限に留める
この場合、shadcn の CLI 再生成によるアップデートに追従する運用が前提になります。
2. コードの特性を理解して取り込む
- atoms の受け入れ基準が「shadcn にあるか」ではなく「headless UI + cva パターンに沿っているか」になる
- shadcn にないコンポーネントも同じ設計原則で自前作成できる
- shadcn は初期コードの供給元という位置づけに下がる
この場合、設計の拠り所は shadcn ではなく 取り込んだコードが持つ特性そのもの になります。
1 と 2 は排他ではなく段階的な移行
この2つの捉え方は、どちらか一方を最初から選ぶものではありません。
プロジェクトの初期段階では、スタイルのカスタムよりも機能の立ち上げが優先されます。この時点では shadcn をそのまま使い、素早くUIを構築するのが合理的です。
プロダクトが成熟しデザインの独自性が求められる段階で、スタイルを自前管理に切り替えます。
初期: shadcn をそのまま使う(速度優先)
↓ プロダクトの成熟・デザイン要件の明確化
成熟期: コード特性を理解して取り込む(デザインの独自性)
重要なのは、どの段階で 2 に移行するかを意識しておくことです。移行のトリガーは「shadcn のデフォルトスタイルがプロダクトのデザイン要件と合わなくなったとき」であり、そのとき初めてコードの所有権を明確にすればよいのです。
判断の軸:UIのデザインは特定のUIライブラリに追従すべきか
ここで問いを一つ立てます。
サービスのUIデザインは、特定のUIライブラリのデザイン変更に追従すべきか?
答えが No であるなら、選択肢は自ずと2に向かいます。
shadcn/ui のスタイルをそのまま使い続けるということは、shadcn のデザイン判断にサービスの見た目を委ねることを意味します。自分たちのサービスのデザインを自分たちで決定するなら、スタイルは自前で所有・管理すべきです。
依存すべきは 振る舞い(headless UI) であり、見た目(スタイル) ではありません。
primitives 層は必要か
Atomic Design で shadcn/ui を扱うとき、もう一つの設計判断があります。
shadcn のコードを primitives(原本)として保持し、atoms をその薄いラッパーにするか。
primitives → atoms パターン
Radix UI → shadcn コード(primitives) → atoms(薄いラッパー) → molecules
このパターンが有効なのは、shadcn のスタイルに追従する場合です。原本を保持することで、CLI 再生成による差分管理が可能になります。
atoms に直接配置するパターン
Radix UI → atoms(shadcn コードを直接配置・カスタム) → molecules
スタイルをカスタムする場合、primitives を保持する意味がありません。cva のバリアント定義やクラスの変更は atoms の責務そのものであり、中間層を挟むと以下の問題が生じます。
| 問題 | 内容 |
|---|---|
| 責務の空洞化 | atoms が re-export するだけの層になる |
| 二重抽象 | Radix → primitives → atoms と3層の間接参照が生まれる |
| 名前と実態の乖離 | 実質的な atoms(スタイル定義 + headless UI ラップ)は primitives にあり、atoms ディレクトリにあるものは atoms ではなくなる |
| YAGNI | headless UI の差し替えに備えた抽象化は、実際にはほぼ発生しない |
トレードオフ:UIライブラリを移行する場合はどうするのか
「primitives 層を設けないと、headless UI を差し替えるとき困るのでは?」という懸念があります。
結論から言えば、atoms 層の内部構造だけを差し替えれば済みます。
ファイル構成による関心の分離
atoms 内でスタイル定義を分離しておくことで、移行時の影響範囲を最小化できます。
atoms/Button/
├── index.tsx # headless UI のラップ + 構造(差し替え対象)
├── index.variants.tsx # cva によるバリアント定義(維持)
└── index.stories.tsx # Storybook(維持)
移行時に何が起きるか
| ファイル | Radix → 別ライブラリ移行時 |
|---|---|
index.tsx |
差し替え(headless UI の構造を書き換える) |
index.variants.tsx |
維持(スタイル定義はライブラリに依存しない) |
index.stories.tsx |
維持(外部インターフェースが変わらなければそのまま) |
なぜ primitives 層が不要なのか
薄いラッパーで抽象化しなくても、ファイルレベルの関心分離で同じ効果が得られます。 ポイントは「スタイル定義」と「headless UI の構造」を同一ファイルに混ぜないことです。
primitives 層による抽象化(不要):
Radix → primitives → atoms → molecules
ファイルレベルの関心分離(採用):
Radix → atoms/index.tsx(構造)
atoms/index.variants.tsx(スタイル)
→ molecules
これにより、抽象層を増やすことなく、移行時には index.tsx だけを書き換えれば済みます。
molecules 以上の層は atoms の公開インターフェースにのみ依存しているため、影響を受けません。
結論
以下の意思決定を行いました。
- shadcn/ui のコードは atoms に直接配置する(primitives 層は設けない)
- shadcn CLI の再生成によるアップデートは行わない
- 内部の headless UI(Radix UI)のアップデートで運用する
- スタイルは自分たちのものとして所有・カスタムする
依存関係:
atoms ──依存──→ Radix UI(振る舞い)
atoms ──所有──→ cva + Tailwind(スタイル)
shadcn/ui は「良い初期コードを効率よく得る手段」であり、取り込んだ後は自分たちのコードです。設計の拠り所は shadcn ではなく、headless UI + cva という構成パターンに置きます。
この判断の根底にあるのは、シンプルな原則です。
サービスのデザインは、自分たちで決める。