shadcn/ui入門 - 再利用可能なUIコンポーネントを作る

初級 | 40分 で読める | 2025.12.13

公式ドキュメント

今回やること

shadcn/uiは、完成品パッケージを読み込むのではなく、コンポーネントのコードを自分のプロジェクトへ追加して使うUI集です。

導入後のコンポーネントは自分たちのコードになります。見た目だけでなく、責任範囲とアクセシビリティを確認する必要があります。

この記事では次を学びます。

  • shadcn/uiセットアップ
  • コンポーネント追加
  • テーマカスタマイズ
  • フォーム構築
  • ダークモード

Step 1: セットアップ

npx create-next-app@latest my-app --typescript --tailwind
cd my-app
npx shadcn-ui@latest init

初期設定で以下を選択:

  • TypeScript: yes
  • Style: Default
  • Base color: Slate
  • CSS variables: yes

Step 2: コンポーネント追加

# 個別追加
npx shadcn-ui@latest add button
npx shadcn-ui@latest add card
npx shadcn-ui@latest add input
npx shadcn-ui@latest add form

# 複数追加
npx shadcn-ui@latest add button card input dialog

Step 3: 基本的な使用

ポイント: shadcn/uiのコンポーネントはプロジェクト内にコピーされるため、自由にカスタマイズ可能です。npmパッケージとは異なり、完全な所有権を持てます。

import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';

export default function HomePage() {
 return (
 <div className="p-8">
 <Card className="max-w-md">
 <CardHeader>
 <CardTitle>ログイン</CardTitle>
 </CardHeader>
 <CardContent className="space-y-4">
 <Input placeholder="メールアドレス" />
 <Input type="password" placeholder="パスワード" />
 <Button className="w-full">ログイン</Button>
 </CardContent>
 </Card>
 </div>
 );
}

実践メモ: variantプロパティでボタンの見た目を簡単に切り替えられます。デザインシステムの一貫性を保つのに最適です。

Step 4: バリアント

<Button variant="default">Default</Button>
<Button variant="destructive">Delete</Button>
<Button variant="outline">Outline</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>

<Button size="sm">Small</Button>
<Button size="default">Default</Button>
<Button size="lg">Large</Button>

Step 5: フォーム構築

npx shadcn-ui@latest add form
npm install zod react-hook-form @hookform/resolvers
'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, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { Input } from '@/components/ui/input';

const schema = z.object({
 email: z.string().email('有効なメールアドレスを入力'),
 password: z.string().min(8, '8文字以上'),
});

export function LoginForm() {
 const form = useForm({
 resolver: zodResolver(schema),
 defaultValues: { email: '', password: '' },
 });

 const onSubmit = (data: z.infer<typeof schema>) => {
 console.log(data);
 };

 return (
 <Form {...form}>
 <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
 <FormField
 control={form.control}
 name="email"
 render={({ field }) => (
 <FormItem>
 <FormLabel>メール</FormLabel>
 <FormControl>
 <Input {...field} />
 </FormControl>
 <FormMessage />
 </FormItem>
 )}
 />
 <FormField
 control={form.control}
 name="password"
 render={({ field }) => (
 <FormItem>
 <FormLabel>パスワード</FormLabel>
 <FormControl>
 <Input type="password" {...field} />
 </FormControl>
 <FormMessage />
 </FormItem>
 )}
 />
 <Button type="submit" className="w-full">ログイン</Button>
 </form>
 </Form>
 );
}

Step 6: ダイアログ

import {
 Dialog,
 DialogContent,
 DialogDescription,
 DialogHeader,
 DialogTitle,
 DialogTrigger,
} from '@/components/ui/dialog';

export function ConfirmDialog() {
 return (
 <Dialog>
 <DialogTrigger asChild>
 <Button variant="destructive">削除</Button>
 </DialogTrigger>
 <DialogContent>
 <DialogHeader>
 <DialogTitle>本当に削除しますか?</DialogTitle>
 <DialogDescription>
 この操作は取り消せません。
 </DialogDescription>
 </DialogHeader>
 <div className="flex justify-end gap-2">
 <Button variant="outline">キャンセル</Button>
 <Button variant="destructive">削除</Button>
 </div>
 </DialogContent>
 </Dialog>
 );
}

注意: テーマのCSS変数を変更する際は、lightモードとdarkモードの両方を必ず更新してください。片方だけ変更すると、モード切替時に表示が崩れます

Step 7: テーマカスタマイズ

/* globals.css */
@layer base {
 :root {
 --primary: 222.2 47.4% 11.2%;
 --primary-foreground: 210 40% 98%;
 /* カスタムカラー */
 }

 .dark {
 --primary: 210 40% 98%;
 --primary-foreground: 222.2 47.4% 11.2%;
 }
}

導入後に整えること

shadcn/uiは、追加したコンポーネントのコードがプロジェクト内に入ります。そのため、導入して終わりではなく、自分たちのUI規約に合わせて調整し、再利用しやすい形に整えることが重要です。

特にフォーム、ダイアログ、ドロップダウンはアクセシビリティに関わるため、見た目だけで判断しないようにします。キーボード操作、フォーカス移動、エラー表示まで確認すると、実用的なUIになります。

カスタマイズの基準

  • 色や余白はデザイントークンに寄せる
  • コンポーネントを直接改変する前に用途を確認する
  • 同じパターンを何度も書かないようにラップする
  • 追加したコードの責任範囲をチームで共有する

動作確認

コンポーネントを追加したら、次を確認します。

確認項目OKの状態
表示light/darkの両方で崩れない
キーボード操作Tab、Enter、Escで操作できる
フォーカス現在位置が視覚的に分かる
エラー表示Formのエラーが入力欄と対応している
再利用同じUIを何度もコピペしていない

DialogやDropdownは、閉じる操作、フォーカス移動、読み上げ名を必ず確認します。

練習

次のログインフォームを作ってください。

  1. ButtonInput を追加する
  2. Card の中にフォームを配置する
  3. Zodでメールアドレスとパスワードを検証する
  4. 送信失敗時のエラー表示を出す
  5. darkモードでも読めるか確認する

次のステップ

フォーム検証は Zodバリデーション入門 と組み合わせると実装しやすくなります。Tailwindの基礎は Tailwind CSS実践ガイドTailwindチートシート を確認してください。

まとめ

shadcn/uiはコピー&ペースト型のUIライブラリで、完全なカスタマイズ性を持ちます。Radix UIとTailwind CSSをベースにしています。

参考リソース

← 一覧に戻る
PR
PR
PR
PR