※本記事には広告(楽天アフィリエイト)リンクが含まれる場合があります。

「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要約しながら読み進めるのもおすすめです。

今すぐshadcn/uiを試してみよう

公式ドキュメントには全コンポーネントのライブデモが揃っています。

shadcn/ui公式サイトを見る →