ブログ記事

shadcn/ui完全マスター 2025 - コンポーネントライブラリの革命

shadcn/uiは89.5kスターを誇る革新的なUIコンポーネントシステム。npmパッケージではなくコピー&ペーストで完全なカスタマイズ性を実現。Radix UIとTailwind CSSで構築された美しくアクセシブルなコンポーネントを今すぐ体験。

Web開発
shadcn/ui React UI Components Tailwind CSS Radix UI
shadcn/ui完全マスター 2025 - コンポーネントライブラリの革命のヒーロー画像

2025 年の Web 開発において、shadcn/uiはコンポーネントライブラリの概念を根本から覆しました。 「これはコンポーネントライブラリではありません。これは、あなたがコンポーネントライブラリを構築する方法です」 という哲学のもと、開発者に真の自由と柔軟性を提供しています。

この記事で学べること

  • shadcn/ui の革新的なアプローチと哲学
  • 30 以上のコンポーネントの詳細解説
  • 各フレームワークでの導入方法
  • カスタマイズとテーマ化のテクニック
  • エンタープライズレベルの実装パターン

shadcn/uiの革命的なアプローチ

従来のコンポーネントライブラリの問題点

これまでのコンポーネントライブラリは、以下のような問題を抱えていました:

従来のコンポーネントライブラリの一般的な問題点
問題点 説明 影響
ブラックボックス化 npmパッケージの内部実装が不透明 カスタマイズが困難
バージョン依存 ライブラリの更新に縛られる 破壊的変更のリスク
スタイルの制約 ライブラリ固有のデザインに縛られる ブランド統一が難しい
バンドルサイズ 未使用コンポーネントも含まれる パフォーマンス低下
ロックイン 特定のエコシステムに依存 移行が困難

shadcn/uiの革新:「Open Code」アプローチ

shadcn/ui は、これらの問題を根本的に解決する新しいアプローチを提案しました:

shadcn/uiのアーキテクチャ

チャートを読み込み中...

コンポーネントのソースコードを直接あなたのプロジェクトにコピーしてください。 それがあなたのコードです。変更し、拡張し、所有してください。

shadcn Creator of shadcn/ui

人気の秘密:コミュニティの支持

GitHub統計から見る成長

GitHubスター数 (89.5k) 90 %
使用プロジェクト数 (26.3k) 26 %
フォーク数 (6.2k) 6 %
コミュニティ活発度 100 %
完了

採用企業・プロジェクト

大手企業からスタートアップまで、幅広く採用されています:

  • Vercel
  • Cal.com
  • Radix 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

components.jsonの設定

{
  "$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"
  }
}

コンポーネントカタログ

基本UIコンポーネント

ボタンコンポーネント

プライマリ、セカンダリ、デストラクティブなどのバリアント

カードコンポーネント

情報をグループ化して表示

入力フィールド

テキスト入力、パスワード、数値入力に対応

セレクトボックス

アクセシブルなドロップダウン選択

ダイアログ

モーダルウィンドウの実装

コンポーネントの追加方法

# 単一コンポーネントの追加
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>
  )
}

フォームの実装

// 多くのコードと複雑なステート管理 import { useState } from 'react' function Form() { const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [errors, setErrors] = useState({}) const validate = () => { // バリデーションロジック } return ( <form> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} /> {errors.email && <span>{errors.email}</span>} // 以下省略... </form> ) }
// React Hook Formと統合されたシンプルな実装 import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import * as z from "zod" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" const formSchema = z.object({ email: z.string().email("有効なメールアドレスを入力してください"), password: z.string().min(8, "パスワードは8文字以上です"), }) function LoginForm() { const form = useForm<z.infer<typeof formSchema>>({ resolver: zodResolver(formSchema), defaultValues: { email: "", password: "", }, }) function onSubmit(values: z.infer<typeof formSchema>) { console.log(values) } return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> <FormField control={form.control} name="email" render={({ field }) => ( <FormItem> <FormLabel>メールアドレス</FormLabel> <FormControl> <Input placeholder="name@example.com" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <Button type="submit">ログイン</Button> </form> </Form> ) }
従来のフォーム
// 多くのコードと複雑なステート管理 import { useState } from 'react' function Form() { const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [errors, setErrors] = useState({}) const validate = () => { // バリデーションロジック } return ( <form> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} /> {errors.email && <span>{errors.email}</span>} // 以下省略... </form> ) }
shadcn/uiのフォーム
// React Hook Formと統合されたシンプルな実装 import { useForm } from "react-hook-form" import { zodResolver } from "@hookform/resolvers/zod" import * as z from "zod" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" const formSchema = z.object({ email: z.string().email("有効なメールアドレスを入力してください"), password: z.string().min(8, "パスワードは8文字以上です"), }) function LoginForm() { const form = useForm<z.infer<typeof formSchema>>({ resolver: zodResolver(formSchema), defaultValues: { email: "", password: "", }, }) function onSubmit(values: z.infer<typeof formSchema>) { console.log(values) } return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> <FormField control={form.control} name="email" render={({ field }) => ( <FormItem> <FormLabel>メールアドレス</FormLabel> <FormControl> <Input placeholder="name@example.com" {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <Button type="submit">ログイン</Button> </form> </Form> ) }

カスタマイズとテーマ化

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%;
    
    --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)
  })
}

アクセシビリティへの取り組み

Radix UIとの統合

shadcn/ui は、Radix UI を基盤としているため、高いアクセシビリティを実現しています:

shadcn/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 のクラスが動作しない

解決方法:

  1. tailwind.config.jscontent 配列に shadcn/ui コンポーネントのパスを追加
  2. PostCSS 設定の確認
  3. 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}',
  ],
  // ...
}

shadcn/uiの将来

ロードマップ

新コンポーネント

ファイルアップロード、リッチテキストエディタなど

AI統合

AIモデルによるコンポーネント生成支援

マルチフレームワーク

Vue、Angularなどへの対応拡大

コミュニティの成長

現在 89.5k のスターを誇る shadcn/ui は、今後も急速に成長を続けることが予想されます。 特に以下の点が注目されています:

  • エンタープライズ向け機能の強化
  • テーマカスタマイズのさらなる柔軟性
  • コミュニティ主導のコンポーネント開発

まとめ

shadcn/ui は、コンポーネントライブラリの新しいパラダイムを提示し、 開発者に真の自由と柔軟性を与えました。以下の点で特に優れています:

  • 完全なコントロール: ソースコードを直接所有し、自由にカスタマイズ
  • 高品質なデザイン: 美しく、アクセシブルで、モダンな UI
  • 依存関係の最小化: npmパッケージの更新に縛られない
  • 優れた開発者体験: CLI で簡単にコンポーネントを追加
  • 活発なコミュニティ: 89.5k のスターが示す高い評価

「これはコンポーネントライブラリではありません」という哲学は、 単なるキャッチフレーズではなく、Web 開発の未来を示しています。

今すぐ始める

  1. 公式サイトでコンポーネントを探索
  2. プロジェクトに shadcn/ui をセットアップ
  3. 必要なコンポーネントを追加してカスタマイズ
  4. 自分だけのコンポーネントライブラリを構築

この記事は役に立ちましたか?

Daily Hackでは、開発者の皆様に役立つ情報を毎日発信しています。