shadcn/ui完全マスター2025 - コンポーネントライブラリの革命
shadcn/uiはコピー&ペーストで使えるReactコンポーネントライブラリの革命。Radix UIとTailwind CSSを基盤に、美しくアクセシブルなコンポーネントを提供。特徴的なコード配布アプローチ、CLIセットアップ、カスタマイズ、実践的なプロジェクト構築まで完全ガイド。
shadcn/uiは89.5kスターを誇る革新的なUIコンポーネントシステム。npmパッケージではなくコピー&ペーストで完全なカスタマイズ性を実現。Radix UIとTailwind CSSで構築された美しくアクセシブルなコンポーネントを今すぐ体験。
2025 年の Web 開発において、shadcn/uiはコンポーネントライブラリの概念を根本から覆しました。 「これはコンポーネントライブラリではありません。これは、あなたがコンポーネントライブラリを構築する方法です」 という哲学のもと、開発者に真の自由と柔軟性を提供しています。
これまでのコンポーネントライブラリは、以下のような問題を抱えていました:
問題点 | 説明 | 影響 |
---|---|---|
ブラックボックス化 | npmパッケージの内部実装が不透明 | カスタマイズが困難 |
バージョン依存 | ライブラリの更新に縛られる | 破壊的変更のリスク |
スタイルの制約 | ライブラリ固有のデザインに縛られる | ブランド統一が難しい |
バンドルサイズ | 未使用コンポーネントも含まれる | パフォーマンス低下 |
ロックイン | 特定のエコシステムに依存 | 移行が困難 |
shadcn/ui は、これらの問題を根本的に解決する新しいアプローチを提案しました:
チャートを読み込み中...
コンポーネントのソースコードを直接あなたのプロジェクトにコピーしてください。 それがあなたのコードです。変更し、拡張し、所有してください。
大手企業からスタートアップまで、幅広く採用されています:
Next.jsでのセットアップ
# 新しいNext.jsプロジェクトを作成
npx create-next-app@latest my-app --typescript --tailwind --eslint
cd my-app
# shadcn/uiを初期化
npx shadcn@latest init
# 設定プロンプト
# - TypeScriptを使用しますか? Yes
# - スタイルを選択: Default
# - ベースカラーを選択: Slate
# - CSS変数を使用しますか? Yes
Viteでのセットアップ
# 新しいViteプロジェクトを作成
npm create vite@latest my-app -- --template react-ts
cd my-app
# Tailwind CSSをインストール
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
# shadcn/uiを初期化
npx shadcn@latest init
Remixでのセットアップ
# 新しいRemixプロジェクトを作成
npx create-remix@latest my-app
cd my-app
# Tailwind CSSを設定
npm install -D tailwindcss
# shadcn/uiを初期化
npx shadcn@latest init
Astroでのセットアップ
# 新しいAstroプロジェクトを作成
npm create astro@latest my-app
cd my-app
# ReactとTailwindを追加
npx astro add react tailwind
# shadcn/uiを初期化
npx shadcn@latest init
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}
プライマリ、セカンダリ、デストラクティブなどのバリアント
情報をグループ化して表示
テキスト入力、パスワード、数値入力に対応
アクセシブルなドロップダウン選択
モーダルウィンドウの実装
# 単一コンポーネントの追加
npx shadcn@latest add button
# 複数コンポーネントの追加
npx shadcn@latest add dialog card select
# すべてのコンポーネントを追加
npx shadcn@latest add --all
// app/dashboard/page.tsx
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { CalendarIcon, CreditCard, Settings, Users } from "lucide-react"
export default function DashboardPage() {
return (
<div className="flex-1 space-y-4 p-8 pt-6">
<div className="flex items-center justify-between space-y-2">
<h2 className="text-3xl font-bold tracking-tight">ダッシュボード</h2>
<div className="flex items-center space-x-2">
<Button>ダウンロード</Button>
</div>
</div>
<Tabs defaultValue="overview" className="space-y-4">
<TabsList>
<TabsTrigger value="overview">概要</TabsTrigger>
<TabsTrigger value="analytics">分析</TabsTrigger>
<TabsTrigger value="reports">レポート</TabsTrigger>
<TabsTrigger value="notifications">通知</TabsTrigger>
</TabsList>
<TabsContent value="overview" className="space-y-4">
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
総収益
</CardTitle>
<CreditCard className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">¥4,532,100</div>
<p className="text-xs text-muted-foreground">
先月比 +20.1%
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
ユーザー数
</CardTitle>
<Users className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">+2,350</div>
<p className="text-xs text-muted-foreground">
先月比 +180.1%
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
売上
</CardTitle>
<CreditCard className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">+12,234</div>
<p className="text-xs text-muted-foreground">
先月比 +19%
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
アクティブユーザー
</CardTitle>
<Users className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">+573</div>
<p className="text-xs text-muted-foreground">
先周比 +201
</p>
</CardContent>
</Card>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
<Card className="col-span-4">
<CardHeader>
<CardTitle>最近のアクティビティ</CardTitle>
</CardHeader>
<CardContent>
{/* グラフやチャートをここに追加 */}
<div className="h-[350px] flex items-center justify-center text-muted-foreground">
チャートエリア
</div>
</CardContent>
</Card>
<Card className="col-span-3">
<CardHeader>
<CardTitle>最近の売上</CardTitle>
<CardDescription>
今月は265件の売上がありました。
</CardDescription>
</CardHeader>
<CardContent>
{/* 売上リストをここに追加 */}
<div className="space-y-8">
<div className="flex items-center">
<div className="ml-4 space-y-1">
<p className="text-sm font-medium leading-none">
田中 太郎
</p>
<p className="text-sm text-muted-foreground">
tanaka@example.com
</p>
</div>
<div className="ml-auto font-medium">+¥19,900</div>
</div>
</div>
</CardContent>
</Card>
</div>
</TabsContent>
</Tabs>
</div>
)
}
/* 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%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-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: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
/* その他のダークモード色定義 */
}
}
// lib/themes.ts
export const themes = {
default: {
name: "Default",
cssVars: {
light: {
background: "0 0% 100%",
foreground: "222.2 84% 4.9%",
primary: "222.2 47.4% 11.2%",
// ...
},
dark: {
background: "222.2 84% 4.9%",
foreground: "210 40% 98%",
primary: "210 40% 98%",
// ...
},
},
},
blue: {
name: "Blue",
cssVars: {
light: {
background: "0 0% 100%",
foreground: "222.2 84% 4.9%",
primary: "221.2 83.2% 53.3%",
// ...
},
dark: {
background: "222.2 84% 4.9%",
foreground: "210 40% 98%",
primary: "217.2 91.2% 59.8%",
// ...
},
},
},
// 他のテーマ...
}
// テーマを適用する関数
export function applyTheme(theme: keyof typeof themes) {
const root = document.documentElement
const themeConfig = themes[theme]
const isDark = root.classList.contains('dark')
const cssVars = isDark ? themeConfig.cssVars.dark : themeConfig.cssVars.light
Object.entries(cssVars).forEach(([key, value]) => {
root.style.setProperty(`--${key}`, value)
})
}
shadcn/ui は、Radix UI を基盤としているため、高いアクセシビリティを実現しています:
機能 | 説明 | メリット |
---|---|---|
キーボードナビゲーション | 完全なキーボード操作に対応 | マウス不要の操作 |
スクリーンリーダー対応 | ARIA属性が適切に設定 | 視覚障害者のサポート |
フォーカス管理 | 適切なフォーカストラップ | 使いやすいUI |
アニメーション設定 | モーションを減らす設定可能 | ユーザーの好みに対応 |
WCAG準拠 | コントラスト比の適切な設定 | 法的要件を満たす |
// コンポーネントの選択的インポート
// 必要なコンポーネントのみをインポート
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
// import { Accordion } from "@/components/ui/accordion" // 未使用
// Tree Shakingにより未使用コンポーネントはバンドルに含まれない
// 大きなコンポーネントの遅延ロード
import dynamic from 'next/dynamic'
const DataTable = dynamic(
() => import('@/components/ui/data-table').then(mod => mod.DataTable),
{
loading: () => <div>読み込み中...</div>,
ssr: false,
}
)
export function Dashboard() {
return (
<div>
{/* 初期表示に必要なコンポーネント */}
<Card>...</Card>
{/* 遅延ロードされるコンポーネント */}
<DataTable />
</div>
)
}
// components/ui/custom/branded-button.tsx
import { Button, ButtonProps } from "@/components/ui/button"
import { cn } from "@/lib/utils"
export interface BrandedButtonProps extends ButtonProps {
brand?: 'primary' | 'secondary' | 'accent'
}
export function BrandedButton({
brand = 'primary',
className,
...props
}: BrandedButtonProps) {
return (
<Button
className={cn(
brand === 'primary' && 'bg-brand-primary hover:bg-brand-primary/90',
brand === 'secondary' && 'bg-brand-secondary hover:bg-brand-secondary/90',
brand === 'accent' && 'bg-brand-accent hover:bg-brand-accent/90',
className
)}
{...props}
/>
)
}
// components/ui/custom/button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react'
import { BrandedButton } from './branded-button'
const meta: Meta<typeof BrandedButton> = {
title: 'UI/BrandedButton',
component: BrandedButton,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
brand: {
control: 'select',
options: ['primary', 'secondary', 'accent'],
},
},
}
export default meta
type Story = StoryObj<typeof meta>
export const Primary: Story = {
args: {
brand: 'primary',
children: 'プライマリボタン',
},
}
export const Secondary: Story = {
args: {
brand: 'secondary',
children: 'セカンダリボタン',
},
}
問題: Tailwind CSS のクラスが動作しない
解決方法:
tailwind.config.js
の content
配列に shadcn/ui コンポーネントのパスを追加globals.css
に Tailwind ディレクティブが含まれているか確認// tailwind.config.js
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./src/**/*.{js,ts,jsx,tsx,mdx}',
],
// ...
}
ファイルアップロード、リッチテキストエディタなど
AIモデルによるコンポーネント生成支援
Vue、Angularなどへの対応拡大
現在 89.5k のスターを誇る shadcn/ui は、今後も急速に成長を続けることが予想されます。 特に以下の点が注目されています:
shadcn/ui は、コンポーネントライブラリの新しいパラダイムを提示し、 開発者に真の自由と柔軟性を与えました。以下の点で特に優れています:
「これはコンポーネントライブラリではありません」という哲学は、 単なるキャッチフレーズではなく、Web 開発の未来を示しています。