shadcn/ui とは
shadcn/ui は、React + Tailwind CSS を使った再利用可能なコンポーネント集です。
開発者が UI を素早く整えつつ、柔軟にカスタマイズできるように設計されています。
「UI コンポーネントライブラリ」ではなく、「コピーしてカスタマイズする前提のテンプレート集」という考え方です。
特徴 | 説明 |
---|---|
Tailwind CSSベース | Tailwind でスタイルを完全にカスタマイズ可能。独自のデザインシステムに柔軟に対応。 |
Radix UI 利用 | アクセシビリティが高く、UI の動作が安定している。モーダル、ドロップダウンなどに強い。 |
CLI で導入可能 | CLI でコンポーネントを簡単に追加できる。 |
オープンソース | コンポーネントのコードが全て見える&編集可能で、プロジェクトに完全に組み込める。 |
テーマ対応 | ダークモードやカスタムカラーなど、テーマの切り替えがしやすい。 |
利用方法
1. 初期セットアップ
プロジェクト直下で以下のコマンドを実行します。
npx shadcn-ui@latest init
2. コンポーネント追加
Card コンポーネントを追加してみます。
shadcn/ui のドキュメントを参照し、「Installation」に記載してあるコマンドを実行します。
npx shadcn@latest add card
実行すると /components/ui/card.tsx
が生成されます。
demo: Card コンポーネントで記事一覧ページ作成
ブログサイトの記事一覧ページを、Card コンポーネントを利用して作成します。
プロジェクトは Next.js/TypeScript + Prisma で構築します。
my-project/
├── prisma/
│ ├── schema.prisma
│ └── seed.cjs
└── src/
├── app/
│ ├── page.tsx
│ └── contents/
│ └── [id]/
│ └── page.tsx
├── components/
│ ├── post/
│ │ └── list.tsx
│ └── ui/
│ └── card.tsx
└── lib/
└── prisma.ts
1. Prisma モデルの設定
Prisma の設定方法や使い方についてはこちらの記事をご参照ください。
スキーマ定義は以下の通りです。
model User {
id String @id @default(cuid())
userName String
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id String @id @default(cuid())
title String
content String
createdAt DateTime @default(now())
authorId String
author User @relation(fields: [authorId], references: [id])
}
2. Card コンポーネントでコンテンツを作成
Card コンポーネントのドキュメントの「Usage」を参考に、コンポーネントを作っていきます。
まず、日付フォーマットで使用するライブラリとして、date-fns
をインストールしておきます。
npm install date-fns@^4
記事一覧ページから呼び出すコンポーネントを作成します。
本題から少し外れますが、今回は Next.js 15 の App Router を使っているため、画面遷移用フック「useRouter」を 'next/navigation' からインポートします。
'next/router' にも useRouter は存在しますが、こちらは Page Router 用のフックです。
import { formatDistanceToNow } from "date-fns";
import { ja } from "date-fns/locale";
import { useRouter } from "next/navigation";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
type Props = {
title: string;
id: string;
createdAt: Date;
content: string;
authorId: string;
author: {
id: string;
userName: string;
createdAt: Date;
};
};
const PostList = (post: Props) => {
const router = useRouter();
return (
<Card
className="cursor-pointer hover:shadow-lg transition-shadow"
onClick={() => router.push(`/contents/${post.id}`)}
>
<CardHeader>
<CardTitle>{post.title}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm mb-2 line-clamp-3">{post.content}</p>
<div className="flex items-center justify-between text-sm text-gray-300">
<span>{post.author.userName}</span>
<time>
{/* 「5分前」「2日前」などの相対時刻を日本語で表示 */}
{formatDistanceToNow(new Date(post.createdAt), {
addSuffix: true,
locale: ja,
})}
</time>
</div>
</CardContent>
</Card>
);
};
export { PostList };
3. 記事一覧ページから呼び出す
import { PostList } from "@/components/post/list";
import { prisma } from "@/lib/prisma";
export default async function Home() {
const posts = await prisma.post.findMany({
orderBy: { createdAt: "desc" },
include: { author: true },
});
return (
<div className="container mx-auto px-5 py-10">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{posts.map((post) => (
<PostList key={post.id} {...post} />
))}
</div>
</div>
);
}
ここまでの実装で、Card コンポーネントで作成したコンテンツを表示できます。
4. コンテンツをクリックして画面遷移
Card コンポーネントに useRouter
を設定することで、コンテンツをクリックすると contents/[id]
ページに遷移させることができます。
Card コンポーネントの遷移機能は実装済みなので、あとは遷移先のページを作成しておきます。
import { format } from "date-fns";
import { ja } from "date-fns/locale";
import { prisma } from "@/lib/prisma";
type Params = {
params: Promise<{ id: string }>;
};
const page = async ({ params }: Params) => {
const { id } = await params;
const post = await prisma.post.findUnique({
where: { id },
include: { author: true },
});
return (
<div className="container mx-auto px-8 py-10">
<div className="flex justify-between items-center mb-4">
<p className="text-sm text-gray-500">投稿者: {post?.author.userName}</p>
<time className="text-sm text-gray-500">
{format(new Date(post?.createdAt ?? 0), "yyyy年MM月dd日", {
locale: ja,
})}
</time>
</div>
<h1>{post?.title}</h1>
<br />
<p>{post?.content}</p>
</div>
);
};
export default page;
ここまでの実装で、一覧ページのコンテンツをクリックすると、ID に紐づく情報が取得されたページに遷移できます。
ちなみに、Next.js 15 ではリンク先を事前に取得して画面遷移を高速化できる Link コンポーネントが用意されています。
こちらは、RSC・RCC の両方で使用できます。
今回使用した useRouter による画面遷移では、Link コンポーネントのようなプリフェッチ機能はありません。
import Link from "next/link";
const page = () => {
return (
<div>
<p>サーバーコンポーネント</p>
<Link href={"/about"}>Aboutページへ移動</Link>
</div>
);
};
export default page;
まとめ
本記事では、shadcn/ui の概要や特徴、実際の利用方法についてデモを交えて紹介しました。
Tailwind CSS と Radix UI を活用した高品質な UI コンポーネントを手軽に導入でき、美しいデザインを素早く整えたい場面で非常に有用です。