T-CREATOR

shadcn/ui とは?Next.js 開発を加速する最強 UI ライブラリ徹底解説

shadcn/ui とは?Next.js 開発を加速する最強 UI ライブラリ徹底解説

Next.js アプリケーションを開発していて、美しく使いやすい UI コンポーネントを効率的に実装したいと考えたことはありませんか?

従来の UI ライブラリでは、デザインが画一的になってしまったり、カスタマイズが複雑すぎたりと、開発者の悩みは尽きません。そんな中、2023 年頃から注目を集めているのが「shadcn/ui」です。

この革新的なコンポーネントコレクションは、従来のライブラリの課題を解決し、Next.js 開発を劇的に加速させる可能性を秘めています。今回は、shadcn/ui の基本から実践的な導入方法まで、初心者の方にもわかりやすく解説いたします。

shadcn/ui とは何か

shadcn/ui の基本概念

shadcn/ui は、従来の「ライブラリ」という概念を覆す、全く新しいアプローチの UI コンポーネントコレクションです。

開発者の shadcn 氏によって作られたこのプロジェクトは、美しく実用的なコンポーネントを「コピー&ペースト」で自分のプロジェクトに組み込める画期的なシステムなのです。各コンポーネントは完全にカスタマイズ可能で、プロジェクトの要件に合わせて自由に改変できます。

shadcn/ui の最大の特徴は、コンポーネントの完全な所有権を開発者に与えることでしょう。一度プロジェクトに追加したコンポーネントは、あなたのものになり、どのように変更しても構いません。

従来の UI ライブラリとの違い

従来の UI ライブラリと shadcn/ui の違いを表で整理してみましょう。

#項目従来のライブラリshadcn/ui
1インストール方法npm installコンポーネント単位でコピー
2依存関係大量のパッケージ最小限の依存関係
3カスタマイズテーマ設定や CSS 上書き完全な所有権とカスタマイズ
4バンドルサイズライブラリ全体を含む使用するコンポーネントのみ
5更新管理パッケージ更新で自動手動管理(完全制御)

最も重要な違いは、shadcn/ui が「ライブラリ」ではなく「コンポーネントコレクション」だということです。つまり、npm でインストールする依存関係ではなく、必要なコンポーネントだけを選んでプロジェクトに直接追加する仕組みなのです。

copy & paste という革新的なアプローチ

shadcn/ui の革新性は、その「copy & paste」アプローチにあります。

従来のライブラリでは、以下のような流れでコンポーネントを使用していました:

typescript// 従来のアプローチ
import { Button } from 'some-ui-library';

function App() {
  return <Button variant='primary'>クリック</Button>;
}

一方、shadcn/ui では次のような流れになります:

typescript// shadcn/uiのアプローチ
// 1. CLIコマンドでコンポーネントを追加
// npx shadcn-ui@latest add button

// 2. 自分のプロジェクト内のコンポーネントとして使用
import { Button } from '@/components/ui/button';

function App() {
  return <Button variant='default'>クリック</Button>;
}

この仕組みには以下のような図解で表現できる流れがあります。

mermaidflowchart LR
  dev[開発者] -->|"npx shadcn-ui add"| cli[shadcn-ui CLI]
  cli -->|コンポーネント生成| project[プロジェクト]
  project -->|完全な所有権| dev
  dev -->|自由にカスタマイズ| project

図からも分かるように、shadcn/ui は開発者とコンポーネントの間に直接的な関係を築きます。中間層(ライブラリ)がないため、開発者は完全な制御権を持てるのです。

Next.js 開発での課題

コンポーネントライブラリ選択の悩み

Next.js 開発において、UI ライブラリの選択は非常に重要な決断です。

多くの開発者が直面する選択肢には以下があります:

  • Material-UI (MUI):豊富な機能だがカスタマイズが複雑
  • Chakra UI:使いやすいが独自のデザインシステム
  • Ant Design:企業向けだが日本のデザイントレンドと合わない場合がある
  • 自作コンポーネント:完全な自由度だが開発コストが膨大

それぞれにメリット・デメリットがあり、プロジェクトの要件、チームのスキル、長期的な保守性を考慮した選択が必要でした。特に、デザインの独自性を求める場合と開発速度を重視する場合のバランスを取ることが困難だったのです。

デザインシステムの構築コスト

企業やプロダクト開発では、一貫したデザインシステムの構築が不可欠です。

しかし、ゼロからデザインシステムを構築するには以下のような膨大なコストがかかります:

typescript// 自作コンポーネントの例(Button)
interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  loading?: boolean;
  // ... 数十個のprops定義
}

// スタイル定義
const buttonStyles = {
  base: 'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2',
  variants: {
    variant: {
      primary:
        'bg-primary text-primary-foreground hover:bg-primary/90',
      // ... 複雑なスタイル定義
    },
  },
};

このような定義を全てのコンポーネントで行い、さらにアクセシビリティ、レスポンシブ対応、ダークモード対応まで考慮すると、数ヶ月から数年の開発期間が必要になってしまいます。

カスタマイズ性と開発速度のトレードオフ

従来の UI ライブラリでは、常にトレードオフが存在していました。

mermaidgraph LR
  fast[開発速度重視] -->|選択| standard[標準ライブラリ使用]
  custom[カスタマイズ重視] -->|選択| selfmade[自作コンポーネント]
  standard -->|結果| limited[デザイン制約]
  selfmade -->|結果| slow[開発速度低下]

開発速度を重視すれば既存ライブラリの制約を受け入れる必要があり、カスタマイズ性を重視すれば開発速度が犠牲になる。この二項対立が、多くの開発チームを悩ませてきました。

特にスタートアップや小規模チームでは、リソースの制約からどちらかを選択せざるを得ず、プロダクトの成長に応じて技術的負債となるケースも少なくありませんでした。

shadcn/ui が解決する問題

完全なカスタマイズ性

shadcn/ui の最大の強みは、完全なカスタマイズ性にあります。

一度プロジェクトに追加されたコンポーネントは、完全にあなたの所有物となり、どのような変更も可能です。例えば、Button コンポーネントをカスタマイズする場合:

typescript// components/ui/button.tsx(shadcn/uiから追加後)
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';

// 基本のvariant定義
const buttonVariants = cva(
  'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors',
  {
    variants: {
      variant: {
        default:
          'bg-primary text-primary-foreground hover:bg-primary/90',
        secondary:
          'bg-secondary text-secondary-foreground hover:bg-secondary/80',
      },
    },
  }
);

この基本定義に対して、プロジェクト固有の要件に合わせて自由に拡張できます:

typescript// カスタマイズ例:独自のvariantを追加
const buttonVariants = cva(
  'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors',
  {
    variants: {
      variant: {
        default:
          'bg-primary text-primary-foreground hover:bg-primary/90',
        secondary:
          'bg-secondary text-secondary-foreground hover:bg-secondary/80',
        // プロジェクト固有のvariantを追加
        brand:
          'bg-gradient-to-r from-purple-500 to-pink-500 text-white hover:from-purple-600 hover:to-pink-600',
        danger: 'bg-red-500 text-white hover:bg-red-600',
      },
    },
  }
);

このようなカスタマイズは、外部ライブラリでは不可能だった柔軟性を提供します。

TypeScript 完全対応

shadcn/ui は、TypeScript ファーストで設計されており、型安全性を完全に保証します。

typescript// 型定義の例
interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
}

// 使用例(IDEで完全な型補完が効く)
<Button
  variant='secondary' // 型安全にvariantを選択
  size='lg' // 型安全にsizeを選択
  onClick={handleClick}
>
  クリックしてください
</Button>;

この型安全性により、開発時にミスを防げるだけでなく、チーム開発での一貫性も保てます。IDE での補完機能も完璧に動作するため、開発体験が大幅に向上します。

Tailwind CSS との完璧な統合

shadcn/ui は、Tailwind CSS との統合を前提に設計されています。

typescript// Tailwind CSSのユーティリティクラスを活用
const buttonVariants = cva(
  // ベースクラス(Tailwindのユーティリティクラス)
  'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
  {
    variants: {
      variant: {
        // Tailwindクラスで直感的にスタイリング
        default:
          'bg-primary text-primary-foreground hover:bg-primary/90',
        destructive:
          'bg-destructive text-destructive-foreground hover:bg-destructive/90',
      },
    },
  }
);

Tailwind CSS の設定ファイルとも完全に連携します:

javascript// tailwind.config.js
module.exports = {
  content: ['./src/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {
      colors: {
        // shadcn/uiが参照するカラー定義
        primary: {
          DEFAULT: 'hsl(var(--primary))',
          foreground: 'hsl(var(--primary-foreground))',
        },
      },
    },
  },
};

この統合により、デザインシステム全体の一貫性を保ちながら、柔軟なカスタマイズが可能になります。

依存関係の軽減

従来の UI ライブラリと比較して、shadcn/ui は驚くほど軽量です。

依存関係の比較を見てみましょう:

#ライブラリ主な依存関係数バンドルサイズ(概算)
1Material-UI20+ packages500KB+
2Chakra UI15+ packages300KB+
3Ant Design25+ packages600KB+
4shadcn/ui3-5 packages必要な分のみ

shadcn/ui の最小構成では、以下のような軽量な依存関係のみです:

json{
  "dependencies": {
    "@radix-ui/react-slot": "^1.0.2",
    "class-variance-authority": "^0.7.0",
    "clsx": "^2.0.0",
    "lucide-react": "^0.294.0",
    "tailwind-merge": "^2.0.0"
  }
}

必要なコンポーネントだけを追加するため、プロジェクトのバンドルサイズを最小限に抑えることができます。パフォーマンスを重視するモダンな Web アプリケーション開発において、これは非常に重要な要素です。

具体的な導入手順

プロジェクトセットアップ

shadcn/ui の導入は、驚くほど簡単です。まずは基本的な Next.js プロジェクトに shadcn/ui を追加する手順を見てみましょう。

既存の Next.js プロジェクト、または新規プロジェクトから始めます:

bash# 新規Next.jsプロジェクトの場合
npx create-next-app@latest my-app --typescript --tailwind --eslint
cd my-app

次に、shadcn/ui の初期設定を行います:

bash# shadcn/ui CLIの初期化
npx shadcn-ui@latest init

このコマンドを実行すると、対話式の設定が開始されます。以下のような質問に答えていきます:

bashWould you like to use TypeScript (recommended)? › yes
Which style would you like to use? › Default
Which color would you like to use as base color? › Slate
Where is your global CSS file? › app/globals.css
Would you like to use CSS variables for colors? › yes
Where is your tailwind.config.js located? › tailwind.config.js
Configure the import alias for components? › @/components
Configure the import alias for utils? › @/lib/utils

設定完了後、プロジェクト構造は以下のようになります:

perlmy-app/
├── components/
│   └── ui/           # shadcn/uiコンポーネントがここに追加される
├── lib/
│   └── utils.ts      # ユーティリティ関数
├── app/
│   ├── globals.css   # Tailwind CSSとカスタムCSS変数
│   └── layout.tsx
└── tailwind.config.js # Tailwind CSS設定(shadcn/ui用に拡張済み)

基本コンポーネントの追加

セットアップが完了したら、実際にコンポーネントを追加してみましょう。最も基本的な Button コンポーネントから始めます:

bash# Buttonコンポーネントを追加
npx shadcn-ui@latest add button

このコマンドにより、components​/​ui​/​button.tsxファイルが自動生成されます。生成されたファイルの構造を見てみましょう:

typescript// 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 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
  {
    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 hover:text-accent-foreground',
        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}
      />
    );
  }
);
Button.displayName = 'Button';

export { Button, buttonVariants };

この Button コンポーネントをアプリケーションで使用してみましょう:

typescript// app/page.tsx
import { Button } from '@/components/ui/button';

export default function Home() {
  return (
    <div className='flex gap-4 p-8'>
      <Button>デフォルト</Button>
      <Button variant='secondary'>セカンダリー</Button>
      <Button variant='outline'>アウトライン</Button>
      <Button variant='ghost'>ゴースト</Button>
      <Button size='sm'>小サイズ</Button>
      <Button size='lg'>大サイズ</Button>
    </div>
  );
}

複数のコンポーネントを一度に追加することも可能です:

bash# よく使用されるコンポーネントをまとめて追加
npx shadcn-ui@latest add button input label card dialog

カスタマイズ方法

shadcn/ui の真価は、カスタマイズの自由度にあります。いくつかの実践的なカスタマイズ例を見てみましょう。

カスタム variant の追加

プロジェクト固有のデザイン要件に合わせて、新しい variant を追加できます:

typescript// components/ui/button.tsx(カスタマイズ版)
const buttonVariants = cva(
  'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
  {
    variants: {
      variant: {
        // 既存のvariant
        default:
          'bg-primary text-primary-foreground hover:bg-primary/90',
        // プロジェクト固有のvariantを追加
        brand:
          'bg-gradient-to-r from-blue-600 to-purple-600 text-white hover:from-blue-700 hover:to-purple-700 shadow-lg',
        success:
          'bg-green-600 text-white hover:bg-green-700',
        warning:
          'bg-yellow-500 text-black hover:bg-yellow-600',
      },
    },
  }
);

グローバルテーマのカスタマイズ

CSS 変数を使用してアプリケーション全体のテーマをカスタマイズできます:

css/* app/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    /* カスタムカラーパレット */
    --background: 0 0% 100%;
    --foreground: 222.2 84% 4.9%;

    --primary: 221.2 83.2% 53.3%;
    --primary-foreground: 210 40% 98%;

    /* ブランド固有の色を追加 */
    --brand-primary: 220 100% 50%;
    --brand-secondary: 280 100% 70%;
  }

  .dark {
    /* ダークモード対応 */
    --background: 222.2 84% 4.9%;
    --foreground: 210 40% 98%;

    --primary: 217.2 91.2% 59.8%;
    --primary-foreground: 222.2 84% 4.9%;
  }
}

コンポーネントの拡張

既存のコンポーネントを拡張して、プロジェクト固有の機能を追加することも可能です:

typescript// components/ui/enhanced-button.tsx
import { Button, ButtonProps } from './button';
import { Loader2 } from 'lucide-react';

interface EnhancedButtonProps extends ButtonProps {
  loading?: boolean;
  icon?: React.ReactNode;
}

export function EnhancedButton({
  loading,
  icon,
  children,
  disabled,
  ...props
}: EnhancedButtonProps) {
  return (
    <Button disabled={disabled || loading} {...props}>
      {loading ? (
        <Loader2 className='mr-2 h-4 w-4 animate-spin' />
      ) : icon ? (
        <span className='mr-2'>{icon}</span>
      ) : null}
      {children}
    </Button>
  );
}

このようなカスタマイズにより、プロジェクトの要件に完璧に合致したコンポーネントシステムを構築できます。

まとめ

shadcn/ui は、従来の UI ライブラリが抱えていた課題を根本的に解決する革新的なアプローチです。

主なメリット

  • 完全なカスタマイズ性:コンポーネントの完全な所有権
  • 軽量性:必要なものだけを追加する設計
  • TypeScript 完全対応:型安全性の保証
  • Tailwind CSS 統合:現代的なスタイリング手法
  • 開発効率:copy & paste による高速開発

Next.js 開発において、shadcn/ui は開発速度とカスタマイズ性の両方を実現する理想的な選択肢です。特に、デザインシステムの構築コストを削減しながら、独自性のある UI を実現したい場合には最適でしょう。

従来の「ライブラリかフルスクラッチか」という二択から脱却し、「コンポーネントを所有しながら高速開発する」という新しいパラダイムを提供してくれます。

今後の Next.js 開発において、shadcn/ui は重要な選択肢の一つとなることは間違いありません。まずは小さなプロジェクトから始めて、その便利さを実感してみてください。

関連リンク