class-variance-authority (CVA) の使い方完全ガイド
一言でいうと
class-variance-authority(CVA) は、UIコンポーネントのクラス名バリアント(variants)を型安全に定義・管理するためのユーティリティライブラリです。Tailwind CSSなどのユーティリティファーストCSSと組み合わせることで、コンポーネントのスタイルバリエーションを宣言的かつ堅牢に扱えます。
どんな時に使う?
- Tailwind CSSでコンポーネントライブラリを構築するとき — Button、Badge、Alertなど、サイズ・カラー・状態ごとにクラスが分岐するコンポーネントを整理したい場合
- 条件分岐だらけのclassName生成を解消したいとき — 三項演算子やテンプレートリテラルが入り乱れたclassName文字列をスッキリさせたい場合
- デザインシステムのバリアントをTypeScriptで型安全に管理したいとき — propsとスタイルの対応関係を型レベルで保証し、タイポや指定漏れを防ぎたい場合
インストール
# npm
npm install class-variance-authority
# yarn
yarn add class-variance-authority
# pnpm
pnpm add class-variance-authority
基本的な使い方
最も典型的なユースケースである、Buttonコンポーネントのバリアント定義を見てみましょう。
// button.ts
import { cva, type VariantProps } from "class-variance-authority";
const button = cva(
// ベースクラス(全バリアント共通)
"inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none",
{
variants: {
intent: {
primary: "bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500",
secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500",
danger: "bg-red-600 text-white hover:bg-red-700 focus:ring-red-500",
},
size: {
sm: "h-8 px-3 text-sm",
md: "h-10 px-4 text-base",
lg: "h-12 px-6 text-lg",
},
},
defaultVariants: {
intent: "primary",
size: "md",
},
}
);
// 使用例
button();
// => "inline-flex items-center ... bg-blue-600 text-white ... h-10 px-4 text-base"
button({ intent: "danger", size: "lg" });
// => "inline-flex items-center ... bg-red-600 text-white ... h-12 px-6 text-lg"
Reactコンポーネントに組み込む場合:
// Button.tsx
import React from "react";
import { cva, type VariantProps } from "class-variance-authority";
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none disabled:opacity-50",
{
variants: {
intent: {
primary: "bg-blue-600 text-white hover:bg-blue-700",
secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300",
danger: "bg-red-600 text-white hover:bg-red-700",
},
size: {
sm: "h-8 px-3 text-sm",
md: "h-10 px-4 text-base",
lg: "h-12 px-6 text-lg",
},
},
defaultVariants: {
intent: "primary",
size: "md",
},
}
);
// VariantPropsで型を自動抽出
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
VariantProps<typeof buttonVariants>;
const Button: React.FC<ButtonProps> = ({
intent,
size,
className,
...props
}) => {
return (
<button
className={buttonVariants({ intent, size, className })}
{...props}
/>
);
};
export { Button, buttonVariants };
よく使うAPI
1. cva() — バリアント定義の本体
CVAの中核関数です。第1引数にベースクラス、第2引数にバリアント設定オブジェクトを渡します。
import { cva } from "class-variance-authority";
const badge = cva("rounded-full font-semibold", {
variants: {
color: {
info: "bg-blue-100 text-blue-800",
success: "bg-green-100 text-green-800",
warning: "bg-yellow-100 text-yellow-800",
},
size: {
sm: "px-2 py-0.5 text-xs",
md: "px-3 py-1 text-sm",
},
},
defaultVariants: {
color: "info",
size: "sm",
},
});
badge({ color: "success", size: "md" });
// => "rounded-full font-semibold bg-green-100 text-green-800 px-3 py-1 text-sm"
2. compoundVariants — 複合条件のスタイル指定
複数のバリアントの組み合わせに応じて追加クラスを適用できます。
const button = cva("rounded px-4 py-2 font-medium", {
variants: {
intent: {
primary: "bg-blue-600 text-white",
secondary: "bg-gray-200 text-gray-900",
},
outline: {
true: "border-2 bg-transparent",
},
},
compoundVariants: [
{
intent: "primary",
outline: true,
className: "border-blue-600 text-blue-600 hover:bg-blue-50",
},
{
intent: "secondary",
outline: true,
className: "border-gray-400 text-gray-700 hover:bg-gray-50",
},
],
defaultVariants: {
intent: "primary",
outline: false,
},
});
button({ intent: "primary", outline: true });
// => "rounded px-4 py-2 font-medium border-2 bg-transparent border-blue-600 text-blue-600 hover:bg-blue-50"
3. VariantProps<T> — バリアントの型を自動抽出
cva で定義したバリアントからTypeScriptの型を自動生成します。コンポーネントのprops定義に不可欠です。
import { cva, type VariantProps } from "class-variance-authority";
const alert = cva("p-4 rounded-lg border", {
variants: {
severity: {
info: "bg-blue-50 border-blue-200 text-blue-800",
error: "bg-red-50 border-red-200 text-red-800",
success: "bg-green-50 border-green-200 text-green-800",
},
},
defaultVariants: {
severity: "info",
},
});
type AlertVariants = VariantProps<typeof alert>;
// => { severity?: "info" | "error" | "success" | null | undefined }
4. className パラメータ — 追加クラスのマージ
cva が返す関数にはバリアントに加えて className を渡せます。外部から追加のクラスを注入する際に使います。
const card = cva("rounded-lg shadow-md p-6", {
variants: {
variant: {
elevated: "shadow-xl",
outlined: "shadow-none border border-gray-200",
},
},
defaultVariants: {
variant: "elevated",
},
});
card({ variant: "outlined", className: "mt-4 max-w-md" });
// => "rounded-lg shadow-md p-6 shadow-none border border-gray-200 mt-4 max-w-md"
注意: CVA自体はクラスの競合解決(例:
shadow-mdとshadow-noneの重複)を行いません。Tailwind CSSの競合解決が必要な場合は、tailwind-mergeと組み合わせてください(後述)。
5. ベースクラスを null にする — バリアントのみの定義
共通クラスが不要な場合、第1引数に null を渡せます。
const textColor = cva(null, {
variants: {
color: {
brand: "text-blue-600",
muted: "text-gray-500",
danger: "text-red-600",
},
},
defaultVariants: {
color: "brand",
},
});
textColor({ color: "muted" });
// => "text-gray-500"
類似パッケージとの比較
| 特徴 | class-variance-authority | clsx / classnames | tailwind-variants | Stitches |
|---|---|---|---|---|
| バリアント定義 | ✅ 宣言的に定義 | ❌ 条件分岐を手書き | ✅ 宣言的に定義 | ✅ 宣言的に定義 |
| TypeScript型推論 | ✅ VariantProps | ❌ なし | ✅ あり | ✅ あり |
| compoundVariants | ✅ | ❌ | ✅ | ✅ |
| クラス競合解決 | ❌(要 tailwind-merge) | ❌ | ✅ 内蔵(tailwind-merge) | N/A(CSS-in-JS) |
| CSS手法 | ユーティリティCSS全般 | 汎用 | Tailwind CSS特化 | CSS-in-JS |
| バンドルサイズ | ~2KB | ~1KB | ~8KB | ~8KB |
| フレームワーク依存 | なし | なし | なし | React推奨 |
使い分けの目安:
- CVA — 軽量でフレームワーク非依存。Tailwind以外のユーティリティCSSでも使える
- tailwind-variants — Tailwind CSS特化で、
tailwind-merge内蔵のためクラス競合を自動解決したい場合 - clsx / classnames — バリアント管理は不要で、単純な条件付きクラス結合だけで十分な場合
注意点・Tips
1. tailwind-merge との併用がほぼ必須
CVAはクラス文字列を単純に結合するだけで、Tailwindクラスの競合解決は行いません。className で上書きしたいケースでは tailwind-merge を組み合わせましょう。
// lib/utils.ts — shadcn/ui でも採用されている定番パターン
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// 使用例
import { cn } from "@/lib/utils";
<button className={cn(buttonVariants({ intent: "primary", size: "md" }), "mt-4")} />
2. boolean バリアントの定義方法
true / false をキーにすることで、boolean型のバリアントを定義できます。
const input = cva("border rounded px-3 py-2", {
variants: {
disabled: {
true: "opacity-50 cursor-not-allowed bg-gray-100",
false: "bg-white",
},
fullWidth: {
true: "w-full",
},
},
defaultVariants: {
disabled: false,
fullWidth: false,
},
});
3. Tailwind CSS IntelliSense の設定
VSCodeのTailwind CSS IntelliSenseを cva 内でも有効にするには、.vscode/settings.json に以下を追加します。
{
"tailwindCSS.experimental.classRegex": [
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
]
}
4. defaultVariants は必ず設定する
defaultVariants を省略すると、バリアントを指定しなかった場合にそのバリアントのクラスが一切適用されません。意図しないスタイル欠落を防ぐため、原則としてすべてのバリアントにデフォルト値を設定しましょう。
5. フレームワーク非依存
CVAはReactに依存していません。Vue、Svelte、Solid、素のHTMLなど、あらゆる環境で使えます。
// Svelte の例
<script lang="ts">
import { cva } from "class-variance-authority";
const chip = cva("inline-block rounded-full px-3 py-1 text-sm", {
variants: {
color: {
default: "bg-gray-200 text-gray-800",
active: "bg-blue-600 text-white",
},
},
defaultVariants: { color: "default" },
});
export let active = false;
</script>
<span class={chip({ color: active ? "active" : "default" })}>
<slot />
</span>
まとめ
class-variance-authority(CVA) は、UIコンポーネントのスタイルバリアントを型安全かつ宣言的に管理するための軽量ライブラリです。Tailwind CSSとの相性が抜群で、shadcn/ui