「MUIとshadcn/ui、どっちを使えばいいの?」
この質問、2026年になってもよく見かけます。でも、これは単なるライブラリ選定の話ではありません。コンポーネントライブラリの設計思想の違いを理解しないと、プロジェクトの途中で「これ、詰んでない?」という状況に陥ります。
この記事では、Props-based(MUI/Chakra系)とOwnership Model(shadcn/ui系)の違いを整理し、プロジェクトに合った選び方を解説します。なお、Props Soup vs Compound Componentで扱った「コンポーネント内部の設計」とは別軸の話です。
2つの設計思想:Props-based vs Ownership Model
コンポーネントライブラリは大きく2つの設計思想に分かれます。
Props-based(従来型)
MUI、Chakra UI、Ant Designなどが採用するアプローチです。コンポーネントをnpmでインストールし、propsでカスタマイズする「ブラックボックス」モデル。
// 外部パッケージに依存
import { Button } from '@mui/material'
<Button variant="contained" color="primary" size="large" />
Vercel Academyの記事では、この問題点が指摘されています:
「コンポーネントがあなたの必要なpropsを公開していない場合、強制的にCSSオーバーライドを大量に書くことになる」
Ownership Model(所有型)
shadcn/uiが代表するアプローチです。コンポーネントのソースコードをプロジェクトにコピーし、自分で所有するモデル。
npx shadcn-ui@latest add button
# → src/components/ui/button.tsx が生成される
RedMonkの分析によると、Supabaseがこのモデルを採用した理由は明確です:
「ユーザーがUIをカスタマイズしたい場合、我々がclassNameを実装するのを待つ必要がない。彼らのコードだから」
shadcnを使っていると開発速度が本当に速いんですよね。「このデザインをどのpropsで表現するか」を考える時間がゼロになる。
比較:どちらが優れているか?
| 観点 | Props-based | Ownership Model |
|---|---|---|
| インストール | npm install | CLIでコピー |
| カスタマイズ | props/themeのみ | 制限なし |
| バグ修正 | npm update | 自分で修正 |
| 依存関係 | パッケージに依存 | Radix等のprimitivesのみ |
| 更新の追従 | 自動 | 手動 |
| 向いている用途 | 標準的なUI | 高度なカスタマイズ |
一見するとOwnership Modelが優れているように見えますが、バグ修正は自分で行う必要があるという点は見落とされがちです。
Escape Hatch:Props-basedの「脱出口」
Props-basedライブラリにも、制約から「脱出」するための仕組みがあります。
Guidewireのドキュメント:「Escape Hatchとは、抽象化レイヤーから脱出して、より低レベルの層にアクセスするための意図的な代替手段」
1. asChildパターン(Radix)
Radixが普及させたパターンで、デフォルトのDOM要素を使わず、子要素にpropsと振る舞いを委譲します。
// 通常: Buttonは<button>を描画
<Button>Click</Button>
// asChild: 子要素に委譲
<Button asChild>
<a href="/path">Link styled as Button</a>
</Button>
Jacob Parisの記事によると:
「asChildはRadixが普及させたパターン。as propより柔軟で、追加のpropsを渡す場合も綺麗に書ける」
2. CSS変数の公開
コンポーネント内部の値をCSS変数として公開し、外部から上書き可能にするアプローチ。
/* ライブラリ側 */
.button {
background: var(--button-bg, #3b82f6);
padding: var(--button-padding, 0.5rem 1rem);
}
/* 利用側で上書き */
:root {
--button-bg: #10b981;
}
3. className / style props
トップレベル要素にカスタムクラスを追加できるようにする最もシンプルな方法。
<DatePicker className="my-custom-picker" />
Headless UI:第三の選択肢
Props-basedでもOwnership Modelでもない、振る舞いだけを提供するアプローチもあります。
| ライブラリ | 開発元 | コンポーネント数 | フレームワーク |
|---|---|---|---|
| Headless UI | Tailwind Labs | 16個 | React, Vue |
| Radix UI | WorkOS | 30+個 | Reactのみ |
| React Aria | Adobe | 40+個 | Reactのみ |
Subframeの分析では:
「Radixはshadcnの人気もあり、多くの開発者にとってデフォルトになりつつある」
実際、Vercel、Supabase、Node.jsチームがRadix Primitivesを採用しています。
デザインシステムとの相性問題
ここからが本題です。Props-basedが機能する前提条件があります。
それは「デザインシステム ↔ コンポーネントライブラリが1:1対応している」こと。
理想の状態:
┌────────────────┐ ┌────────────────┐
│ デザインシステム │ ←→ │ コンポーネント │
│ (Figma) │ 1:1 │ ライブラリ │
│ Button/Primary │ === │ <Button primary>│
└────────────────┘ └────────────────┘
この対応が崩れると地獄になります。
Props-basedのプロジェクトで、デザインがデザインシステムのコンポーネントを参照していない場合、本当にきつい。都度デザインを見てスタイルの当て方に悩むことになります。
よくある崩壊パターン
- デザインシステムが未完成:コンポーネントが中途半端
- デザイナーがシステムを参照していない:「なんか丸いボタン」
- ライブラリにないvariantが必要:escape hatchの連発
// こうなりがち
<Button
variant="contained" // これで合ってる?
sx={{
borderRadius: '24px', // デザインに合わせて上書き
backgroundColor: '#xxx', // テーマにない色...
'&:hover': { ... } // hoverも手動
}}
/>
「このデザインをどのpropsで表現するか」を考える時間が、積み重なると膨大になります。
判断基準:どちらを選ぶべきか
| 状況 | 推奨 |
|---|---|
| デザインシステムが完成 + ライブラリが完全カバー | Props-based ✅ |
| コードを完全にコントロールしたい | Ownership Model ✅ |
| 長期運用 + 複数チーム | Props-based(更新管理が楽) |
| MVP/プロトタイプ | Ownership Model(速度重視) |
| ライブラリのAPIに縛られたくない | Ownership Model ✅ |
shadcnが速い理由
デザイン → コード変換の思考が不要になるからです。
// Props-based: デザインをpropsに「翻訳」する作業
<Button
variant="contained"
sx={{ borderRadius: '24px', backgroundColor: '#xxx' }}
/>
// shadcn: デザイン通りに直接書く
<Button className="rounded-full bg-brand-500 hover:bg-brand-600">
ちなみに、「うちのUIライブラリ使ってね」と言われて、いざデザインを見たらそのライブラリにないvariantだらけ…という経験、ありませんか?そういうとき、「最初からshadcnで自由に書かせてくれ」と心の中で叫んでいます。
まとめ
コンポーネントライブラリの選択は、単なる好みの問題ではありません。
- Props-basedは「デザインシステムが完成していて、ライブラリがそれを完全にカバーしている」場合に機能する
- Ownership Modelは「コードを完全にコントロールしたい、ライブラリのAPIに縛られたくない」場合に強い
- Escape Hatch(asChild、CSS変数等)を知っておくと、Props-basedの制約を回避できる
- Headless UIは「振る舞いだけ欲しい」場合の第三の選択肢
ただし、どちらを選んでもチーム内で統一することが最も重要です。混在が一番辛いパターンですから。