はじめに
正直なところ、Reactのshadcn/uiを見て「Vueにもこれ欲しいな」とずっと思っていたんですよ。で、調べてみたらあった。Shadcn-Vue。
これ、意外と知らない人が多いんですけど、GitHubで8,800スター以上を獲得していて、195人以上のコントリビューターが開発に参加しています。2年前に始まったプロジェクトなんですけど、1日平均9つ以上のスターが付いているペースで成長中。
30代になって思うのは、技術選定で「React向けのあれ、Vue版ないかな」と探すことが増えたということ。で、Shadcn-VueはReact版と同じクオリティを維持しているので、Vueユーザーにとっては朗報です。
Shadcn-Vueとは
Shadcn-Vueは、あのshadcn/uiのVueポートです。公式サイトでは「A set of beautifully designed components that you can customize, extend, and build on」と説明されています。
つまり、美しくデザインされたコンポーネントを自分のプロジェクトにコピーして、自由にカスタマイズできる。npmパッケージとしてインストールするのではなく、ソースコードを手元に持ってくるアプローチです。
内部的にはRadix VueとTailwind CSSを使っています。Radix VueはRadix UIのVue版で、アクセシビリティがしっかり確保されている。この辺りはReact版と同じ思想ですね。
MITライセンスのオープンソースで、開発も活発。直近では19時間前にも更新されています。
特徴・メリット
1. コンポーネントが完全に自分のものになる
これがshadcn系の最大のメリット。普通のUIライブラリだと、バージョンアップで破壊的変更が入ったり、「この部分だけ変えたいのに」ができなかったりする。
Shadcn-Vueは、コンポーネントのソースコードを自分のプロジェクトにコピーするので、完全にカスタマイズ可能。依存関係に縛られない自由さがあります。
個人的には、この「所有権」の感覚が一番のメリットだと思います。
2. Radix Vueベースで堅牢
内部的にはRadix Vueを使っているので、アクセシビリティがしっかり確保されている。キーボードナビゲーション、スクリーンリーダー対応など、自分で一から実装すると大変な部分が最初から入っています。
これ、意外と重要なんですけど、アクセシビリティ対応は後から入れると本当に大変。最初から入っているのは時短になる。
3. Tailwind CSSとの完璧な統合
Tailwind CSSユーザーには一択ですね。クラス名でスタイリングできるので、既存のTailwindプロジェクトにスムーズに導入できます。
Tailwind CSS v4にも対応しているので、最新の環境でも問題なく使えます。
4. 複数フレームワーク対応
Vite、Nuxt、Astro、Laravelなど、主要なフレームワークすべてに対応しています。特にNuxtユーザーにとっては、Vue系のUIライブラリ選択肢が増えるのは嬉しいポイント。
5. VSCode拡張機能が便利
公式のVSCode拡張機能があって、CLIの初期化やコンポーネントのインストール、ドキュメント表示がIDEから直接できる。これ、地味に便利なんですよ。
6. TypeScriptで型安全
全コンポーネントがTypeScriptで書かれているので、型推論がしっかり効く。propsの補完が効くのは、開発効率に直結します。
インストール方法
Viteプロジェクトの場合
新規プロジェクトを作成する場合:
pnpm create vite@latest my-vue-app --template vue-ts
Tailwind CSSをインストール:
pnpm add tailwindcss @tailwindcss/vite
src/style.cssにTailwindを読み込み:
@import "tailwindcss";
TypeScriptの設定。tsconfig.jsonとtsconfig.app.jsonにパスエイリアスを追加:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
vite.config.tsでパス解決を設定。事前に@types/nodeをインストールしておく:
pnpm add -D @types/node
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [vue(), tailwindcss()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
Shadcn-Vueの初期化
pnpm dlx shadcn-vue@latest init
対話式でカラー設定などを聞かれるので、プロジェクトに合わせて選択。
コンポーネントの追加
# Buttonコンポーネントを追加
pnpm dlx shadcn-vue@latest add button
# 複数まとめて追加
pnpm dlx shadcn-vue@latest add button card dialog input
追加されたコンポーネントはcomponents/ui/ディレクトリに配置されます。
基本的な使い方
Buttonコンポーネント
<script setup lang="ts">
import { Button } from '@/components/ui/button'
</script>
<template>
<div class="flex gap-4">
<Button>Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="link">Link</Button>
</div>
</template>
variantでスタイルを切り替えられる。React版と同じAPIなので、移行も楽。
サイズの指定
<template>
<Button size="sm">Small</Button>
<Button>Default</Button>
<Button size="lg">Large</Button>
<Button size="icon">
<IconPlus />
</Button>
</template>
icon-smやicon-lgなどのサイズバリエーションも用意されています。
アイコン付きボタン
<script setup lang="ts">
import { Button } from '@/components/ui/button'
import { GitBranchIcon } from 'lucide-vue-next'
</script>
<template>
<Button variant="outline" size="sm">
<GitBranchIcon />
New Branch
</Button>
</template>
アイコンライブラリはlucide-vue-nextが公式で推奨されています。
as-childを使ったリンク
<script setup lang="ts">
import { Button } from '@/components/ui/button'
</script>
<template>
<Button as-child>
<a href="/login">Login</a>
</Button>
</template>
ボタンの見た目でリンクを作りたい時に便利。Vue RouterのRouterLinkと組み合わせることもできます。
Cardコンポーネント
<script setup lang="ts">
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card'
import { Button } from '@/components/ui/button'
</script>
<template>
<Card class="w-[350px]">
<CardHeader>
<CardTitle>プロジェクト作成</CardTitle>
<CardDescription>新しいプロジェクトを作成します</CardDescription>
</CardHeader>
<CardContent>
<p>プロジェクトの詳細を入力してください。</p>
</CardContent>
<CardFooter>
<Button>作成</Button>
</CardFooter>
</Card>
</template>
CardHeader、CardContent、CardFooterで構造化できる。直感的ですよね。
実践的なユースケース
フォームの実装
VeeValidateとZodを使ったフォーム実装が公式で推奨されています。
<script setup lang="ts">
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod'
import { Button } from '@/components/ui/button'
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
const formSchema = toTypedSchema(z.object({
email: z.string().email('有効なメールアドレスを入力してください'),
password: z.string().min(8, 'パスワードは8文字以上で入力してください'),
}))
const { handleSubmit } = useForm({
validationSchema: formSchema,
})
const onSubmit = handleSubmit((values) => {
console.log(values)
})
</script>
<template>
<form @submit="onSubmit" class="space-y-4">
<FormField v-slot="{ componentField }" name="email">
<FormItem>
<FormLabel>メールアドレス</FormLabel>
<FormControl>
<Input type="email" placeholder="email@example.com" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField }" name="password">
<FormItem>
<FormLabel>パスワード</FormLabel>
<FormControl>
<Input type="password" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<Button type="submit" class="w-full">
ログイン
</Button>
</form>
</template>
Zodでバリデーションスキーマを定義して、VeeValidateで状態管理。Vue的な書き方でReact Hook Formと同等のことができます。
ダークモード対応
テーマ機能が組み込まれているので、ダークモード対応も簡単。
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { Button } from '@/components/ui/button'
import { Moon, Sun } from 'lucide-vue-next'
const isDark = ref(false)
onMounted(() => {
isDark.value = document.documentElement.classList.contains('dark')
})
const toggleTheme = () => {
isDark.value = !isDark.value
document.documentElement.classList.toggle('dark', isDark.value)
}
</script>
<template>
<Button variant="ghost" size="icon" @click="toggleTheme">
<Sun v-if="isDark" class="h-5 w-5" />
<Moon v-else class="h-5 w-5" />
</Button>
</template>
Nuxtを使っている場合は@nuxtjs/color-modeと組み合わせるとより便利。
コンポーネントのカスタマイズ
Shadcn-Vueの真価は、コンポーネントを自由にカスタマイズできること。
// components/ui/button/index.ts を直接編集
const buttonVariants = cva(
'inline-flex items-center justify-center ...',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground ...',
// カスタムバリアントを追加
brand: 'bg-gradient-to-r from-blue-500 to-purple-600 text-white hover:opacity-90',
},
size: {
// カスタムサイズを追加
xl: 'h-14 px-8 text-lg',
},
},
}
)
ソースが手元にあるので、何でもできる。この自由度がQOL上がりますね。
React版shadcn/uiとの違い
基本的にはReact版と同じ思想、同じAPI設計。違いは主にVue特有の部分:
| 項目 | Shadcn-Vue | shadcn/ui (React) |
|---|---|---|
| ベースライブラリ | Radix Vue | Radix UI |
| フォーム | VeeValidate | React Hook Form |
| 状態管理 | Vue Composition API | React Hooks |
| フレームワーク | Vite, Nuxt, Astro | Next.js, Vite, Remix |
コンポーネントの見た目や使い方はほぼ同じなので、React版のドキュメントやチュートリアルも参考になります。
まとめ
Shadcn-Vueを導入して感じた変化:
- 開発速度: 美しいUIを素早く作れるようになった
- 自由度: カスタマイズの制限がなくなった
- 保守性: 依存関係のバージョン管理から解放された
- 学習コスト: Tailwind知っていればほぼゼロ
- React資産の活用: React版のナレッジがそのまま使える
30代になって思うのは、「あっちのエコシステムの方が良いツールがあるから」と言語を変えるのはコスト的に割に合わない、ということ。その点で、Shadcn-VueはVueユーザーにとって「Reactに移行しなくても同じクオリティのUIが作れる」選択肢を提供してくれます。
特に「Vue/Nuxtプロジェクトでshadcn/uiみたいなUI作りたい」という人には刺さるはず。まだ試していない人は、公式サイトで実装例を見てみてください。ダッシュボード、タスク管理、認証画面など、実践的なサンプルが揃っています。