※本記事には広告(楽天アフィリエイト)リンクが含まれる場合があります。
「UIコンポーネントをライブラリとしてインストールするのではなく、ソースコードをそのままプロジェクトにコピーして使う」——shadcn/uiはそんなコピペ前提の革新的なアプローチで、2023年の登場からわずか数年でReactエコシステム最大のコンポーネントライブラリに成長しました。
GitHub Star数は2026年時点で8万を超え、Next.jsの公式Starterでも採用。「shadcnを知らないReact開発者はもぐり」と言われるほど普及しています。本記事では、shadcn/uiの基本概念からセットアップ・カスタマイズ・実践テクニックまでをコード付きで完全解説します。
対象読者:React/Next.jsを使ったことがある・Tailwind CSSを触ったことがある方
shadcn/uiとは?——「インストールしないライブラリ」の革命
shadcn/uiは「コンポーネントライブラリではない」と作者自身が言っています。通常のライブラリ(MUI、Chakra UI)は npm install で依存関係として追加しますが、shadcn/uiは違います。
CLIでコンポーネントを追加すると、ソースコードがそのままプロジェクトの components/ui/ に配置されます。つまり:
- コンポーネントの中身が手元にある → 自由に改変・拡張できる
- 外部ライブラリのバージョン変更による破壊的変更がない
- 必要なコンポーネントだけ追加 → バンドルサイズを最小化
- デザインシステムをプロジェクトごとに完全制御できる
内部的には Radix UI(アクセシビリティ対応のHeadlessコンポーネント)と Tailwind CSS を組み合わせています。Radixがロジックとアクセシビリティを担当し、Tailwindがスタイリングを担当するという明確な分離が特徴です。
他ライブラリとの違い
| ライブラリ | スタイル | カスタマイズ性 | アクセシビリティ | バンドルサイズ |
|---|---|---|---|---|
| shadcn/ui | Tailwind CSS | ★★★★★ | ★★★★★ | 最小(使った分だけ) |
| Material UI (MUI) | CSS-in-JS | ★★★ | ★★★★ | 重め |
| Chakra UI | CSS-in-JS | ★★★★ | ★★★★ | 中程度 |
| Headless UI | なし(自前) | ★★★★★ | ★★★★★ | 最小 |
| Ant Design | 独自CSS | ★★ | ★★★ | 非常に重め |
セットアップ:Next.jsへの導入(最速10分)
前提条件
- Node.js 18以上
- Next.js 13以上(App Router推奨)またはVite + React
- Tailwind CSS v3以上(v4でも動作)
手順1:新しいNext.jsプロジェクトを作成
npx create-next-app@latest my-app --typescript --tailwind --eslint --app
cd my-app
手順2:shadcn/ui CLIで初期化
npx shadcn@latest init
対話形式で設定を聞かれます。
Which style would you like to use? › Default
Which color would you like to use as base color? › Slate
Would you like to use CSS variables for colors? › yes
これで components.json(設定ファイル)と lib/utils.ts(ユーティリティ)が生成されます。
手順3:コンポーネントを追加
コンポーネントは必要なものだけ追加します。
# Buttonコンポーネントを追加
npx shadcn@latest add button
# 複数まとめて追加
npx shadcn@latest add button dialog form input label
# 全コンポーネントを一括追加
npx shadcn@latest add --all
追加後、components/ui/button.tsx が生成されます。このファイルはあなたのプロジェクトのものなので、自由に編集できます。
Vite + React プロジェクトへの導入
Next.js以外でも使えます。Vite + Reactの場合:
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
その後、vite.config.ts にパスエイリアスを設定してから npx shadcn@latest init を実行します。
// vite.config.ts
import path from 'path'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})
基本コンポーネントの使い方
Button
最も基本的なButtonコンポーネント。variant と size でスタイルを切り替えられます。
import { Button } from "@/components/ui/button"
export default function Example() {
return (
<div className="flex gap-2">
{/* バリアント */}
<Button>Default</Button>
<Button variant="destructive">削除</Button>
<Button variant="outline">キャンセル</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">リンク</Button>
{/* サイズ */}
<Button size="sm">小さい</Button>
<Button size="lg">大きい</Button>
<Button size="icon">🔔</Button>
{/* ローディング状態 */}
<Button disabled>
<span className="animate-spin mr-2">⟳</span>
処理中...
</Button>
</div>
)
}
Dialog(モーダル)
アクセシビリティ完備のモーダルダイアログ。Radix UI の Dialog プリミティブをラップしています。
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
export function DeleteConfirmDialog() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="destructive">削除する</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>本当に削除しますか?</DialogTitle>
<DialogDescription>
この操作は取り消せません。アカウントとデータが完全に削除されます。
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline">キャンセル</Button>
<Button variant="destructive">削除する</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
Table
データ一覧の表示に使うTableコンポーネント。アクセシビリティを保ちながら柔軟にカスタマイズできます。
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
const users = [
{ id: 1, name: "田中 太郎", email: "tanaka@example.com", role: "Admin" },
{ id: 2, name: "鈴木 花子", email: "suzuki@example.com", role: "User" },
]
export function UserTable() {
return (
<Table>
<TableCaption>ユーザー一覧</TableCaption>
<TableHeader>
<TableRow>
<TableHead>名前</TableHead>
<TableHead>メールアドレス</TableHead>
<TableHead>ロール</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell className="font-medium">{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>{user.role}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
}
Toast(通知)
操作フィードバックのToast通知。useToast フックで簡単に呼び出せます。
// layout.tsx または RootLayoutで Toaster を配置
import { Toaster } from "@/components/ui/toaster"
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Toaster />
</body>
</html>
)
}
// コンポーネントで使用
import { useToast } from "@/components/ui/use-toast"
import { Button } from "@/components/ui/button"
export function NotifyButton() {
const { toast } = useToast()
return (
<Button
onClick={() => {
toast({
title: "保存しました",
description: "変更内容が正常に保存されました。",
})
}}
>
保存
</Button>
)
}
テーマとカスタマイズ:CSS変数で全体を制御
shadcn/uiのテーマは globals.css の CSS変数で管理されています。変数を変えるだけで全コンポーネントの配色が変わるのが強みです。
/* globals.css */
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
/* ... ダークモード用の変数 */
}
}
例えばブランドカラーをグリーン系に変えたい場合:
:root {
--primary: 142.1 76.2% 36.3%; /* green-600 */
--primary-foreground: 355.7 100% 97.3%;
--ring: 142.1 76.2% 36.3%;
}
テーマのプリセットを使う
shadcn/uiには複数のカラーテーマが用意されています。初期化時に選んだテーマ以外に変更したい場合は、公式テーマページからCSS変数をコピー&ペーストするだけです。
- Slate(デフォルト)——落ち着いたグレー系
- Zinc——少し暖かみのあるグレー
- Rose——ピンク/ローズ系のアクセント
- Blue——鮮やかなブルー系
- Orange——オレンジ系のアクセント
ダークモードの実装
shadcn/uiはNext.jsの next-themes との連携が推奨されています。
npm install next-themes
// providers.tsx
'use client'
import { ThemeProvider } from 'next-themes'
export function Providers({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
)
}
// layout.tsx
import { Providers } from './providers'
export default function RootLayout({ children }) {
return (
<html lang="ja" suppressHydrationWarning>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
ダークモード切り替えボタンの実装:
'use client'
import { useTheme } from 'next-themes'
import { Button } from '@/components/ui/button'
import { Moon, Sun } from 'lucide-react'
export function ThemeToggle() {
const { theme, setTheme } = useTheme()
return (
<Button
variant="ghost"
size="icon"
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
>
<Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">テーマを切り替え</span>
</Button>
)
}
React Hook Form + Zodと連携:フォームを型安全に
shadcn/uiの真価が発揮されるのがフォーム実装です。Form コンポーネント + React Hook Form + Zod の組み合わせで、型安全かつアクセシビリティ完備のフォームを作れます。
npm install react-hook-form zod @hookform/resolvers
npx shadcn@latest add form input label
'use client'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
import { Button } from '@/components/ui/button'
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
// Zodスキーマでバリデーションルールを定義
const formSchema = z.object({
username: z
.string()
.min(2, { message: 'ユーザー名は2文字以上で入力してください。' })
.max(50, { message: 'ユーザー名は50文字以内で入力してください。' }),
email: z
.string()
.email({ message: '正しいメールアドレスを入力してください。' }),
password: z
.string()
.min(8, { message: 'パスワードは8文字以上で入力してください。' }),
})
type FormValues = z.infer<typeof formSchema>
export function SignupForm() {
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
username: '',
email: '',
password: '',
},
})
function onSubmit(values: FormValues) {
console.log(values)
// APIコール等の処理
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>ユーザー名</FormLabel>
<FormControl>
<Input placeholder="yamada_taro" {...field} />
</FormControl>
<FormDescription>公開プロフィールに表示される名前です。</FormDescription>
<FormMessage /> {/* バリデーションエラーを自動表示 */}
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>メールアドレス</FormLabel>
<FormControl>
<Input type="email" placeholder="you@example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? '送信中...' : '登録する'}
</Button>
</form>
</Form>
)
}
FormMessage コンポーネントがZodのバリデーションエラーを自動で表示してくれるのがポイントです。エラーメッセージのスタイルやアクセシビリティ(aria-describedby)も自動で対応されています。
よく使うコンポーネント一覧
shadcn/uiには50以上のコンポーネントが用意されています。特によく使うものをまとめました。
| コンポーネント | 用途 | 使用頻度 |
|---|---|---|
Button |
ボタン(variant・sizeで多様なスタイル) | ★★★★★ |
Input / Textarea |
テキスト入力フィールド | ★★★★★ |
Dialog |
モーダルダイアログ | ★★★★★ |
Select |
ドロップダウン選択 | ★★★★ |
Card |
コンテンツカード(Header・Content・Footer) | ★★★★ |
Table |
データテーブル | ★★★★ |
Toast / Toaster |
通知・フィードバックメッセージ | ★★★★ |
DropdownMenu |
ドロップダウンメニュー | ★★★★ |
Sheet |
サイドパネル(モバイルナビ等) | ★★★ |
Tabs |
タブ切り替え | ★★★ |
Badge |
ステータス・ラベル表示 | ★★★ |
Skeleton |
ローディングプレースホルダー | ★★★ |
Command |
コマンドパレット(検索・操作) | ★★★ |
DataTable |
TanStack Table連携の高機能テーブル | ★★★ |
コンポーネントのカスタマイズ実践
Buttonを拡張する
shadcn/uiのコンポーネントはソースコードが手元にあるので、自由に拡張できます。例えば「アイコン付きボタン」バリアントを追加する場合:
// components/ui/button.tsx を直接編集
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center ...",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground ...",
outline: "border border-input bg-background ...",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
// ✅ カスタムバリアントを追加
success: "bg-green-600 text-white hover:bg-green-700",
warning: "bg-yellow-500 text-white hover:bg-yellow-600",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
// 使用例
<Button variant="success">完了</Button>
<Button variant="warning">注意</Button>
cn() ユーティリティの活用
lib/utils.ts にある cn() 関数は clsx と tailwind-merge を組み合わせたもので、条件付きクラス名を安全にマージします。
import { cn } from "@/lib/utils"
// 条件付きクラス
<Button
className={cn(
"w-full",
isLoading && "opacity-50 cursor-not-allowed",
isError && "border-red-500"
)}
>
送信
</Button>
// Tailwindのクラス競合を自動解決
cn("p-4 px-8") // → "px-8"(pxがpの後なので上書き)
cn("text-red-500 text-blue-500") // → "text-blue-500"(後勝ち)
Cardコンポーネントで管理画面ウィジェットを作る
個人開発でよく使う「ダッシュボード風カード」の実装例です。
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
export function StatsCard() {
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">今月の売上</CardTitle>
<Badge variant="secondary">+12.5%</Badge>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">¥45,231</div>
<p className="text-xs text-muted-foreground">
先月比 +¥5,234
</p>
</CardContent>
<CardFooter>
<Button variant="outline" size="sm" className="w-full">
詳細を見る
</Button>
</CardFooter>
</Card>
)
}
よく使うパターンと実装Tips
Combobox(検索付きSelect)
shadcn/uiには Combobox という単独コンポーネントはありませんが、Popover + Command を組み合わせて実装します。
// 公式ドキュメントに完全な実装例あり
// https://ui.shadcn.com/docs/components/combobox
import { Check, ChevronsUpDown } from "lucide-react"
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from "@/components/ui/command"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
const frameworks = [
{ value: "next.js", label: "Next.js" },
{ value: "sveltekit", label: "SvelteKit" },
{ value: "astro", label: "Astro" },
]
DataTable(TanStack Table連携)
大量データの表示には TanStack Table と組み合わせた DataTable パターンが使えます。ソート・フィルタリング・ページネーションを持つ本格的なテーブルです。
npm install @tanstack/react-table
npx shadcn@latest add table
公式ドキュメントの DataTable セクションに、完全なサンプルコードが用意されています。コンポーネントごと丸コピーして、データと列定義を差し替えるだけで動きます。
Skeletonでローディング体験を改善
import { Skeleton } from "@/components/ui/skeleton"
// データフェッチ中のプレースホルダー
function ArticleCardSkeleton() {
return (
<div className="space-y-3">
<Skeleton className="h-4 w-[250px]" />
<Skeleton className="h-4 w-[200px]" />
<Skeleton className="h-32 w-full rounded-lg" />
</div>
)
}
// 使用例(Next.js loading.tsx)
export default function Loading() {
return (
<div className="grid gap-4 md:grid-cols-3">
{Array.from({ length: 6 }).map((_, i) => (
<ArticleCardSkeleton key={i} />
))}
</div>
)
}
shadcn/ui v2(2026年)の新機能
2026年にリリースされたshadcn/ui v2では以下が強化されました。
- Tailwind CSS v4対応 — CSS-first設定へ完全移行。
@import "tailwindcss"一行でOK - Charts コンポーネント — Recharts ベースのグラフコンポーネントが公式サポート
- Sidebar コンポーネント — 折りたたみ可能なサイドバーナビゲーションが追加
- themes.shadcn.ui.com — テーマのビジュアルエディタが提供され、リアルタイムプレビューでテーマを設計できる
- block システム — ページレイアウト全体をまとめたブロック(ダッシュボード・ログインページ等)をCLIで追加できる
# ダッシュボードブロックを一発追加
npx shadcn@latest add dashboard-01
# ログインページブロック
npx shadcn@latest add authentication-01
他のツールとの組み合わせ
shadcn/uiは単体でも強力ですが、他ツールと組み合わせるとさらに効果的です。
- Zod — フォームバリデーション。shadcn/uiのFormと相性抜群
- TypeScript — コンポーネントのprops型が完全に推論される
- Tailwind CSS v4 — v4のCSS変数ファーストな設計とshadcnの相性が向上
- TanStack Query — データ取得状態と組み合わせてSkeleton/Errorコンポーネントを表示
- Lucide React — shadcn/uiと同じ作者が関わるアイコンライブラリ。UIと統一感のあるアイコンが使える
まとめ:shadcn/uiは「所有するコンポーネント」の新しい標準
shadcn/uiの核心は「コンポーネントを所有する」という哲学です。外部ライブラリに依存するのではなく、ソースコードを自分のプロジェクトに取り込み、完全にコントロールする。この考え方は個人開発から企業の大規模プロジェクトまで、幅広いシーンで機能します。
- ✅ コピペでソースが手に入る——自由に改変・拡張できる
- ✅ Radix UI ベース——アクセシビリティが標準装備
- ✅ Tailwind CSS + CSS変数——テーマカスタマイズが直感的
- ✅ React Hook Form + Zod との親和性——型安全フォームが簡単に
- ✅ 必要なものだけ追加——バンドルサイズを最小化
- ✅ 50以上のコンポーネント——ほぼすべてのUIパターンをカバー
2026年現在、Next.jsとshadcn/uiの組み合わせは「Reactで何か作るときのデファクトスタック」になっています。まだ使ったことがない方は、まず npx shadcn@latest init からはじめてみてください。10分でモダンなUIが動き出すはずです。
作ったアプリの公開後は、PagePulse で稼働状況を監視したり、QuickSummary でshadcn/uiの長文ドキュメントをAI要約しながら読み進めるのもおすすめです。