はじめに:なぜshadcn/uiが注目されているのか?
2023年、JavaScriptエコシステムで異例の現象が起きました。「shadcn/ui」が、JavaScript Rising Starsで総合1位を獲得。2位のBunに10,000スター以上の差をつけての圧勝でした。さらに2024年も連続で1位を獲得し、その勢いは止まりません。
なぜこれほどまでに注目されているのでしょうか?
その答えは、shadcn/uiの公式サイトに明確に示されています:
"This is NOT a component library. It's a collection of re-usable components that you can copy and paste into your apps." (これはコンポーネントライブラリではありません。アプリにコピー&ペーストできる再利用可能なコンポーネントのコレクションです。)
今回は、このshadcn/uiが従来のUIライブラリとどう違うのか、なぜ開発者から支持されているのか、そして実際にどう使うのかを、初心者エンジニアにもわかりやすく解説していきます。
shadcn/uiとは何か?基本概念の理解
読み方と名前の由来
まず、「shadcn/ui」は「シャドシーエヌ・ユーアイ」と読みます。開発者のShad氏(@shadcn)の名前から取られています。
従来のUIライブラリとの決定的な違い
従来のUIライブラリ(Material-UI、Ant Design、Chakra UIなど)の仕組み
# 従来のUIライブラリのインストール
npm install @mui/material @emotion/react @emotion/styled
// 使用例:Material-UI
import { Button } from '@mui/material';
function App() {
return <Button variant="contained">Click me</Button>;
}
特徴:
- npmパッケージとしてインストール
- node_modulesに依存
- カスタマイズは限定的(テーマやpropsで調整)
- ライブラリ全体のバンドルサイズが大きい
バンドルサイズとは? バンドル(Bundle)とは、複数のJavaScriptファイルやCSSファイル、その他のリソースを1つまたは少数のファイルにまとめたものです。WebpackやRollupなどの「バンドラー」と呼ばれるツールが、開発時に分かれている多数のファイルを本番環境用に結合・圧縮します。
バンドルサイズとは、このまとめられたファイルの容量のことで、ユーザーがWebサイトを開く際にダウンロードする必要があるデータ量を指します。バンドルサイズが大きいと:
- ページの読み込みが遅くなる
- モバイル環境では特に影響が大きい
- データ通信量が増える
Material-UIのimportについて
import { Button } from '@mui/material'のように特定のコンポーネントだけをimportしても、実はMaterial-UI全体が読み込まれるわけではありません。現代のバンドラーは「Tree Shaking」という機能を持っており、使用されていないコードを自動的に除外します。
ただし、Material-UIの場合:
- Buttonコンポーネント自体が依存する共通コード(テーマシステム、スタイルエンジンなど)も含まれる
- これらの基盤となるコードが約30-40KBある
- 結果として、1つのコンポーネントを使うだけでも相当なサイズになる
shadcn/uiの仕組み
# shadcn/uiのコンポーネント追加
npx shadcn-ui@latest add button
// 使用例:shadcn/ui
// components/ui/button.tsx(自動生成されたファイル)
import * as React from "react"
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 rounded-md text-sm font-medium...",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
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",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
// 使用側
import { Button } from "@/components/ui/button"
function App() {
return (
<div className="space-y-4">
{/* デフォルトのボタン */}
<Button>Click me</Button>
{/* バリアントを指定 */}
<Button variant="destructive">削除</Button>
<Button variant="outline">キャンセル</Button>
<Button variant="secondary">サブアクション</Button>
<Button variant="ghost">ゴースト</Button>
<Button variant="link">リンク</Button>
{/* サイズを指定 */}
<Button size="sm">小さいボタン</Button>
<Button size="lg">大きいボタン</Button>
{/* 組み合わせも可能 */}
<Button variant="outline" size="lg">大きいアウトライン</Button>
</div>
)
}
buttonVariantsの仕組み:
cva(Class Variance Authority)を使って、ボタンのバリアント(種類)を定義しています。これにより:
variantプロパティで見た目のスタイルを切り替え可能sizeプロパティでサイズを調整可能- TypeScriptの型安全性が保証される(存在しないvariantを指定するとエラーになる)
特徴:
- コンポーネントのコードをプロジェクトに直接コピー
- 完全にカスタマイズ可能(コードを直接編集できる)
- 必要なコンポーネントだけを追加
- バンドルサイズを最小限に抑えられる
shadcn/uiの技術スタック
1. Radix UI(ラディックス・ユーアイ)
Radix UIとは?
- スタイルを持たない(Unstyled/Headless)UIコンポーネントライブラリ
- アクセシビリティ(障害を持つ人でも使いやすい設計)が組み込まれている
- WAI-ARIA(Web Accessibility Initiative - Accessible Rich Internet Applications)準拠
アクセシビリティとは? Webアクセシビリティとは、障害の有無や年齢、利用環境などに関わらず、すべての人がWebサイトやアプリケーションを利用できるようにすることです。Radix UIは以下のような機能を自動的に提供します:
// Radix UIの例(スタイルなし)
import * as Dialog from '@radix-ui/react-dialog';
<Dialog.Root>
<Dialog.Trigger>開く</Dialog.Trigger> {/* ← キーボード操作対応 */}
<Dialog.Portal>
<Dialog.Overlay /> {/* ← 背景のクリックで閉じる */}
<Dialog.Content>
{/* ↓ スクリーンリーダー用のタイトル(role="heading"が自動付与) */}
<Dialog.Title>タイトル</Dialog.Title>
{/* ↓ 説明文(aria-describedbyで関連付け) */}
<Dialog.Description>説明文</Dialog.Description>
{/* ↓ Escキーで閉じる、フォーカストラップ対応 */}
<Dialog.Close>閉じる</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
Radix UIが自動的に処理してくれること:
- キーボード操作: Tab、Enter、Escape、矢印キーなどの適切な処理
- フォーカス管理: ダイアログを開いた時のフォーカス移動、閉じた時の復帰
- ARIA属性:
role、aria-label、aria-describedbyなどの自動付与 - スクリーンリーダー対応: 視覚障害者向けの読み上げソフトへの対応
これらを自分で実装すると非常に複雑になりますが、Radix UIを使えば自動的に対応されます。
2. Tailwind CSS(テイルウィンド・シーエスエス)
Tailwind CSSとは?
- ユーティリティファーストのCSSフレームワーク
- クラス名を組み合わせてスタイリングする
<!-- 従来のCSS -->
<button class="btn btn-primary">Click</button>
<style>
.btn { padding: 8px 16px; border-radius: 4px; }
.btn-primary { background-color: blue; color: white; }
</style>
<!-- Tailwind CSS -->
<button class="px-4 py-2 rounded bg-blue-500 text-white">Click</button>
3. Class Variance Authority (CVA)
CVAとは?
- コンポーネントのバリアント(種類)を管理するライブラリ
- TypeScript対応で型安全
const buttonVariants = cva(
// ベーススタイル
"inline-flex items-center justify-center rounded-md",
{
variants: {
// バリアント定義
variant: {
default: "bg-primary text-white",
secondary: "bg-secondary text-black",
ghost: "hover:bg-accent hover:text-accent-foreground",
},
size: {
sm: "h-9 px-3 text-xs",
md: "h-10 px-4 py-2",
lg: "h-11 px-8",
},
},
defaultVariants: {
variant: "default",
size: "md",
},
}
)
なぜshadcn/uiを使うのか?解決される問題と恩恵
問題1:カスタマイズの制限
従来のUIライブラリの問題点:
// Material-UIでボタンをカスタマイズしたい場合
import { Button } from '@mui/material';
import { styled } from '@mui/material/styles';
// スタイルのオーバーライドが必要
const CustomButton = styled(Button)(({ theme }) => ({
backgroundColor: '#custom-color',
'&:hover': {
backgroundColor: '#hover-color',
},
// !importantを使わざるを得ない場合も...
padding: '12px 24px !important',
}));
shadcn/uiの解決策:
// button.tsxを直接編集
const buttonVariants = cva(
"...",
{
variants: {
variant: {
// 新しいバリアントを追加
custom: "bg-gradient-to-r from-purple-500 to-pink-500 text-white shadow-lg",
},
},
}
)
// 使用
<Button variant="custom">カスタムボタン</Button>
なぜこれで問題が解決するのか?
- 直接編集可能: コンポーネントのソースコードがプロジェクト内にあるため、自由に編集できる
- !important不要: ライブラリのスタイルを上書きする必要がないため、CSSの優先順位で悩まない
- TypeScript対応: 新しいバリアントを追加すると、自動的に型定義も更新される
- バージョン依存なし: ライブラリのアップデートでカスタマイズが壊れる心配がない
問題2:バンドルサイズの肥大化
従来のUIライブラリ:
- Material-UI:約100KB
- Ant Design:約300KB+
- Bootstrap:約81KB
shadcn/uiの場合:
- 必要なコンポーネントだけを含める
- Button componentのみ:約2-3KB
- 使わない機能は含まれない
問題3:デザインの画一化
従来の問題:
// どのサイトも似たようなMaterial Designに...
import { Button, Card, TextField } from '@mui/material';
// カスタマイズが難しく、結果的に似たデザインになりがち
shadcn/uiの解決:
// colors.cssで独自のカラースキームを定義
:root {
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 217.2 32.6% 17.5%;
// 完全にオリジナルのデザインシステムを構築可能
}
実践!shadcn/uiの導入とカスタマイズ
Step 1: プロジェクトのセットアップ
# Next.jsプロジェクトの作成
npx create-next-app@latest my-app --typescript --tailwind --app
cd my-app
# shadcn/uiの初期化
npx shadcn-ui@latest init
npx shadcn-ui@latest initの仕組み
npxは、npmパッケージを一時的にダウンロードして実行するコマンドです。shadcn-uiはnpmに公開されているCLI(コマンドラインインターフェース)ツールで、以下の処理を行います:
- プロジェクトの設定ファイルを分析
- 必要な設定ファイルを生成(components.json)
- ユーティリティ関数を作成(lib/utils.ts)
- TailwindCSSの設定を更新
- グローバルCSSにベーススタイルを追加
つまり、shadcn-ui自体はプロジェクトに残らず、初期設定だけを行って終了します。
初期化時の質問:
✔ Would you like to use TypeScript (recommended)? … yes
# TypeScriptを使用するか(推奨)
✔ Which style would you like to use? › Default
# スタイルテーマの選択(Default/New York)
# Defaultは角が丸く、New Yorkはより角張ったデザイン
✔ Which color would you like to use as base color? › Slate
# ベースカラーの選択(Slate/Gray/Zinc/Neutral/Stone)
✔ Where is your global CSS file? … app/globals.css
# グローバルCSSファイルの場所(ベーススタイルを追加する場所)
✔ Do you want to use CSS variables for colors? … yes
# CSS変数を使用するか(ダークモード対応などが簡単になる)
✔ Where is your tailwind.config.js located? … tailwind.config.js
# Tailwind設定ファイルの場所
✔ Configure the import alias for components? … @/components
# コンポーネントのインポートエイリアス(import時のパス)
✔ Configure the import alias for utils? … @/lib/utils
# ユーティリティ関数のインポートエイリアス
Step 2: コンポーネントの追加
# よく使うコンポーネントを追加
npx shadcn-ui@latest add button
npx shadcn-ui@latest add card
npx shadcn-ui@latest add dialog
npx shadcn-ui@latest add form
npx shadcn-ui@latest add input
コンポーネント追加時の動作
npx shadcn-ui@latest add buttonを実行すると:
- shadcn/uiのGitHubリポジトリから最新のButtonコンポーネントのコードを取得
components/ui/button.tsxファイルを作成- 必要な依存関係(Radix UIなど)があれば自動的にnpm installを実行
- 完了後、
import { Button } from "@/components/ui/button"で使用可能になる
実際に作成されるファイルの例:
components/
ui/
button.tsx # ← ここにコンポーネントのコードがコピーされる
card.tsx
dialog.tsx
form.tsx
input.tsx
重要な点は、これらのファイルはあなたのプロジェクトの一部になることです。外部ライブラリへの依存ではなく、プロジェクト内のソースコードとして存在します。
Step 3: 実際のコンポーネント作成例
例1:ログインフォームの作成
// app/login/page.tsx
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export default function LoginPage() {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<Card className="w-[400px]">
<CardHeader>
<CardTitle>ログイン</CardTitle>
<CardDescription>
アカウントにログインしてください
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">メールアドレス</Label>
<Input
id="email"
type="email"
placeholder="example@email.com"
/>
</div>
<div className="space-y-2">
<Label htmlFor="password">パスワード</Label>
<Input
id="password"
type="password"
/>
</div>
</CardContent>
<CardFooter className="flex justify-between">
<Button variant="outline">キャンセル</Button>
<Button>ログイン</Button>
</CardFooter>
</Card>
</div>
)
}
例2:ダークモード対応のダッシュボード
// components/Dashboard.tsx
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Moon, Sun } from "lucide-react"
export default function Dashboard() {
const [isDark, setIsDark] = useState(false)
const toggleTheme = () => {
setIsDark(!isDark)
document.documentElement.classList.toggle('dark')
}
return (
<div className={`min-h-screen ${isDark ? 'dark' : ''}`}>
<div className="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
<header className="p-4 border-b dark:border-gray-700">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold">ダッシュボード</h1>
<Button
variant="ghost"
size="icon"
onClick={toggleTheme}
>
{isDark ? <Sun /> : <Moon />}
</Button>
</div>
</header>
<main className="p-6 grid grid-cols-1 md:grid-cols-3 gap-6">
<Card>
<CardHeader>
<CardTitle>売上</CardTitle>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">¥1,234,567</p>
<p className="text-sm text-gray-500 dark:text-gray-400">
前月比 +12.5%
</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>ユーザー数</CardTitle>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">8,934</p>
<p className="text-sm text-gray-500 dark:text-gray-400">
前月比 +8.2%
</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>コンバージョン率</CardTitle>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">3.45%</p>
<p className="text-sm text-gray-500 dark:text-gray-400">
前月比 +0.3%
</p>
</CardContent>
</Card>
</main>
</div>
</div>
)
}
Step 4: カスタマイズ例
カスタムボタンバリアントの追加
// components/ui/button.tsx を編集
const buttonVariants = cva(
"...",
{
variants: {
variant: {
// 既存のバリアント
default: "...",
destructive: "...",
// 新しいグラデーションバリアントを追加
gradient: "bg-gradient-to-r from-blue-500 to-purple-600 text-white hover:from-blue-600 hover:to-purple-700 shadow-lg",
// ネオンエフェクトバリアント
neon: "bg-transparent border-2 border-cyan-400 text-cyan-400 hover:bg-cyan-400 hover:text-gray-900 transition-all duration-300 shadow-[0_0_10px_rgba(34,211,238,0.5)]",
},
},
}
)
// 使用例
<Button variant="gradient">グラデーションボタン</Button>
<Button variant="neon">ネオンボタン</Button>
カスタムカラーテーマの作成
/* app/globals.css */
@layer base {
:root {
/* カスタムカラーパレット */
--primary: 259 94% 51%; /* 紫 */
--primary-foreground: 0 0% 100%; /* 白 */
--secondary: 199 89% 48%; /* 青 */
--secondary-foreground: 0 0% 100%;
--accent: 339 90% 51%; /* ピンク */
--accent-foreground: 0 0% 100%;
--success: 142 71% 45%; /* 緑 */
--warning: 45 93% 47%; /* オレンジ */
--error: 0 84% 60%; /* 赤 */
}
.dark {
/* ダークモード用のカラー */
--primary: 263 70% 65%;
--secondary: 199 70% 65%;
--accent: 339 70% 65%;
}
}
高度な使い方:フォームとバリデーション
shadcn/uiは、React Hook FormとZodと組み合わせて、型安全なフォームを作成できます。
React Hook Formとは? React Hook Formは、Reactでフォームを扱うためのライブラリです。以下の特徴があります:
- 非制御コンポーネント(Uncontrolled Components)ベースで高パフォーマンス
- 再レンダリングを最小限に抑える
- バリデーション機能が組み込まれている
- TypeScriptとの相性が良い
Zodとは? Zodは、TypeScriptファーストのスキーマ宣言・バリデーションライブラリです:
- 型定義とバリデーションルールを同時に定義
- 実行時の型チェックが可能
- エラーメッセージのカスタマイズが簡単
バリデーションスキーマとは? バリデーションスキーマとは、データが満たすべき条件(ルール)を定義したものです。例えば:
- メールアドレスの形式が正しいか
- パスワードが8文字以上か
- 年齢が18歳以上か
これらのルールをコードで表現したものがバリデーションスキーマです。
// 必要なパッケージをインストール
// npm install react-hook-form zod @hookform/resolvers
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import * as 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"
import { toast } from "@/components/ui/use-toast"
// バリデーションスキーマの定義
const formSchema = z.object({
username: z.string()
.min(2, { message: "ユーザー名は2文字以上である必要があります" })
.max(50, { message: "ユーザー名は50文字以下である必要があります" }),
email: z.string()
.email({ message: "有効なメールアドレスを入力してください" }),
age: z.number()
.min(18, { message: "18歳以上である必要があります" })
.max(120, { message: "年齢が正しくありません" }),
})
export function ProfileForm() {
// フォームの初期化
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
email: "",
age: 18,
},
})
// フォーム送信処理
function onSubmit(values: z.infer<typeof formSchema>) {
toast({
title: "フォームが送信されました",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">
{JSON.stringify(values, null, 2)}
</code>
</pre>
),
})
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>ユーザー名</FormLabel>
<FormControl>
<Input placeholder="john_doe" {...field} />
</FormControl>
<FormDescription>
公開プロフィールに表示される名前です
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>メールアドレス</FormLabel>
<FormControl>
<Input type="email" placeholder="example@email.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="age"
render={({ field }) => (
<FormItem>
<FormLabel>年齢</FormLabel>
<FormControl>
<Input
type="number"
{...field}
onChange={(e) => field.onChange(parseInt(e.target.value))}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">送信</Button>
</form>
</Form>
)
}
AI時代の開発:v0.devとの連携
Vercel社が開発した「v0.dev」は、shadcn/uiを使用したUI生成に特化したAIツールです。
v0.devとは? v0.devは、自然言語(普通の文章)でUIの要望を伝えると、それに応じたReactコンポーネントを自動生成するAIサービスです。特徴:
- shadcn/uiのコンポーネントを使用
- Tailwind CSSでスタイリング
- TypeScript対応
- レスポンシブデザイン対応
料金について
- 無料プラン:月10回まで生成可能
- 有料プラン($20/月):無制限の生成、プライベートプロジェクト
v0.devの使い方
- v0.devにアクセス
- 作りたいUIを自然言語で説明
- AIが生成したコードをコピー
- プロジェクトにペースト
例:プロンプト
"価格プランを表示するカード3つ並べたセクションを作って。
Basic、Pro、Enterpriseの3つのプランで、それぞれ価格と機能リストを含む"
生成されるコード例:
export function PricingCards() {
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 p-6">
<Card>
<CardHeader>
<CardTitle>Basic</CardTitle>
<CardDescription>個人利用に最適</CardDescription>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">¥0</p>
<p className="text-sm text-gray-500">永久無料</p>
<ul className="mt-4 space-y-2">
<li>✓ 基本機能</li>
<li>✓ 5GBストレージ</li>
<li>✓ メールサポート</li>
</ul>
</CardContent>
<CardFooter>
<Button className="w-full">始める</Button>
</CardFooter>
</Card>
{/* Pro、Enterpriseプランも同様 */}
</div>
)
}
shadcn/uiを選ぶべきケース vs 選ばないべきケース
shadcn/uiが適している場合
✅ カスタマイズ性を重視する場合
- 独自のデザインシステムを構築したい
- ブランドガイドラインに厳密に従う必要がある
✅ パフォーマンスを重視する場合
- バンドルサイズを最小限に抑えたい
- 不要な機能を含めたくない
✅ 長期的なメンテナンスを考慮する場合
- ライブラリの更新に依存したくない
- コードの完全な制御権を持ちたい
従来のUIライブラリの長期メンテナンス問題: Material-UIなどの外部ライブラリを使用していると、以下のような問題が発生することがあります:
- 破壊的変更: メジャーバージョンアップで既存コードが動かなくなる(Material-UI v4→v5でAPIが大幅変更)
- 依存関係の競合: 他のライブラリとの依存関係で更新できない
- サポート終了: 古いバージョンのセキュリティアップデートが提供されなくなる
- カスタマイズの破損: ライブラリ更新でカスタマイズ部分が動作しなくなる
shadcn/uiならコードを所有しているため、これらの問題を回避できます。
✅ Tailwind CSSを既に使用している場合
- 既存のTailwindプロジェクトとの親和性が高い
shadcn/uiが適さない場合
❌ すぐに使えるデザインが欲しい場合
- Material DesignやAnt Designのような完成されたデザインシステムが必要
- デザインにこだわりがない
❌ Tailwind CSSを使いたくない場合
- CSS-in-JSを好む
- 既存のCSSフレームワークを使用している
❌ チーム全員がTailwindに慣れていない場合
- 学習コストを考慮する必要がある
よくある質問と回答
Q1: shadcn/uiは無料ですか?
A: はい、完全に無料でオープンソースです。MITライセンスで提供されています。
MITライセンスとは? MITライセンスは最も制限の少ないオープンソースライセンスの1つです:
- 商用利用OK: 企業のプロダクトでも無料で使用可能
- 改変OK: 自由に修正・カスタマイズ可能
- 再配布OK: 修正したコードを公開・販売も可能
- 著作権表示のみ必要: ライセンス文と著作権表示を含めるだけ
- 保証なし: 作者は一切の責任を負わない
つまり、ほぼ何でも自由にできるライセンスです。
Q2: React以外でも使えますか?
A: 現在はReact専用です。ただし、Vue版の「shadcn-vue」やSvelte版の開発も進んでいます。
Q3: 既存のプロジェクトに導入できますか?
A: はい、可能です。ただし、Tailwind CSSの設定が必要です。
なぜTailwind CSSの設定が必要なのか? shadcn/uiのコンポーネントは、Tailwind CSSのクラス名を使ってスタイリングされているためです。例えば:
bg-primary→ 背景色の指定text-sm→ 文字サイズの指定hover:bg-primary/90→ ホバー時のスタイル
これらのクラスが動作するには、Tailwind CSSがプロジェクトに設定されている必要があります。
すでにTailwind CSSを使っているプロジェクトの場合:
# Tailwind CSSがすでに設定されている場合は、shadcn/uiの初期化のみでOK
npx shadcn-ui@latest init
Tailwind CSSが未導入の場合:
# Tailwind CSSのインストールと設定
npm install tailwindcss postcss autoprefixer
npx tailwindcss init -p # tailwind.config.jsとpostcss.config.jsを生成
# その後、shadcn/uiを初期化
npx shadcn-ui@latest init
Q4: コンポーネントの更新はどうすればいいですか?
A: shadcn/uiはコピー&ペースト方式なので、自動更新はありません。必要に応じて手動で最新版をコピーするか、gitで差分を確認して更新します。
Q5: TypeScriptは必須ですか?
A: 必須ではありませんが、強く推奨されています。型安全性のメリットが大きいためです。
パフォーマンス比較
バンドルサイズの実測値
// 実際のプロジェクトでの計測結果
{
"Material-UI (全体)": "約300KB (gzip: 90KB)",
"Ant Design (全体)": "約380KB (gzip: 120KB)",
"Chakra UI (全体)": "約210KB (gzip: 70KB)",
"shadcn/ui (Button + Card + Dialog)": "約15KB (gzip: 5KB)"
}
Lighthouse スコアの比較
Lighthouseとは? Lighthouseは、Googleが提供するWebページの品質測定ツールです。Chrome DevToolsに組み込まれており、以下の項目を0-100点で評価します(高いほど良い):
-
Performance(パフォーマンス): ページの読み込み速度
- First Contentful Paint(最初のコンテンツ表示時間)
- Largest Contentful Paint(最大コンテンツ表示時間)
- Total Blocking Time(メインスレッドのブロック時間)
- Speed Index(ページ表示速度)
-
Accessibility(アクセシビリティ): 障害者への配慮
- 適切なARIA属性の使用
- 十分なカラーコントラスト
- キーボード操作への対応
- スクリーンリーダーへの対応
-
Best Practices(ベストプラクティス): Web標準への準拠
- HTTPS使用
- 安全なJavaScriptライブラリ
- 適切な画像形式
- コンソールエラーの有無
-
SEO(検索エンジン最適化): 検索エンジンへの最適化
- メタタグの設定
- 構造化データ
- モバイル対応
- クロール可能性
// 同じレイアウトを各UIライブラリで実装した場合
{
"shadcn/ui": {
"Performance": 98, // 軽量なため高速
"Accessibility": 100, // Radix UIによる完璧な対応
"Best Practices": 100, // 最新のWeb標準に準拠
"SEO": 100 // 軽量で高速なため評価が高い
},
"Material-UI": {
"Performance": 89, // バンドルサイズが大きいため若干低下
"Accessibility": 98, // 高いが一部カスタマイズで問題が出ることも
"Best Practices": 100,
"SEO": 100
}
}
実践的なTips集
Tip 1: エイリアスの設定
// tsconfig.json
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"],
"@/ui/*": ["./src/components/ui/*"]
}
}
}
Tip 2: カスタムフックの作成
// hooks/use-theme.ts
import { useEffect, useState } from 'react'
export function useTheme() {
const [theme, setTheme] = useState<'light' | 'dark'>('light')
useEffect(() => {
const stored = localStorage.getItem('theme') as 'light' | 'dark'
if (stored) {
setTheme(stored)
document.documentElement.classList.toggle('dark', stored === 'dark')
}
}, [])
const toggleTheme = () => {
const newTheme = theme === 'light' ? 'dark' : 'light'
setTheme(newTheme)
localStorage.setItem('theme', newTheme)
document.documentElement.classList.toggle('dark')
}
return { theme, toggleTheme }
}
Tip 3: コンポーネントの組み合わせパターン
// components/composite/DataTable.tsx
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { useState } from "react"
interface DataTableProps<T> {
data: T[]
columns: {
key: keyof T
label: string
sortable?: boolean
}[]
}
export function DataTable<T>({ data, columns }: DataTableProps<T>) {
const [filter, setFilter] = useState('')
const [sortKey, setSortKey] = useState<keyof T | null>(null)
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc')
const filteredData = data.filter(item =>
Object.values(item as any).some(value =>
String(value).toLowerCase().includes(filter.toLowerCase())
)
)
const sortedData = sortKey
? [...filteredData].sort((a, b) => {
const aVal = a[sortKey] as any
const bVal = b[sortKey] as any
const order = sortOrder === 'asc' ? 1 : -1
return aVal > bVal ? order : -order
})
: filteredData
return (
<div className="space-y-4">
<Input
placeholder="検索..."
value={filter}
onChange={(e) => setFilter(e.target.value)}
className="max-w-sm"
/>
<Table>
<TableCaption>データテーブル</TableCaption>
<TableHeader>
<TableRow>
{columns.map(column => (
<TableHead key={String(column.key)}>
{column.sortable ? (
<Button
variant="ghost"
onClick={() => {
if (sortKey === column.key) {
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
} else {
setSortKey(column.key)
setSortOrder('asc')
}
}}
>
{column.label}
{sortKey === column.key && (
<span className="ml-2">
{sortOrder === 'asc' ? '↑' : '↓'}
</span>
)}
</Button>
) : (
column.label
)}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{sortedData.map((item, index) => (
<TableRow key={index}>
{columns.map(column => (
<TableCell key={String(column.key)}>
{String(item[column.key])}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
)
}
まとめ:shadcn/uiがもたらす新しい開発体験
shadcn/uiは、従来のUIライブラリとは全く異なるアプローチで、開発者に次のような価値を提供しています:
🎯 主なメリット
- 完全な制御権 - コードを直接所有し、自由にカスタマイズ
- 最小限のバンドルサイズ - 必要なものだけを含める
- アクセシビリティ - Radix UIによる堅牢な実装
- すべてのコンポーネントがキーボード操作可能
- スクリーンリーダー対応(NVDA、JAWS、VoiceOver)
- WAI-ARIA仕様に準拠
- フォーカス管理の自動化
- 型安全性 - TypeScriptファーストな設計
- モダンな開発体験 - Tailwind CSSとの完璧な統合
🚀 これからの展望
- AI(v0.dev)との統合がさらに進化
- Vue、Svelte版の充実
- コンポーネントの種類がさらに増加
- エンタープライズ向け機能の充実
💡 導入を検討すべきタイミング
- 新規プロジェクトの立ち上げ時
- デザインシステムの刷新時
- パフォーマンス改善が必要な時
- Tailwind CSSへの移行を検討している時
shadcn/uiは「ライブラリ」ではなく「哲学」です。コンポーネントを「使う」のではなく「所有する」という考え方は、長期的なプロジェクトの保守性と拡張性を大きく向上させます。
2025年のフロントエンド開発において、shadcn/uiは単なるトレンドではなく、新しいスタンダードになりつつあります。この記事を参考に、ぜひあなたのプロジェクトでもshadcn/uiを試してみてください。
参考リンク
Happy Coding with shadcn/ui! 🎨✨