ReactとTailwindの組み合わせ実践
TailwindとReactを組み合わせると、長いクラス列の問題がコンポーネント化で解決できます。この章では実務でよく使うパターン(cn()・cva・動的クラスの注意点)をまとめます。
学習者ボタンの色やサイズをpropsで切り替えたいけど、クラスを条件分岐で組み立てると undefined が混ざったり競合したり…うまくいかない。
先生そこで登場するのが cn() と cva。条件付きのクラス結合と、バリアントの型安全な管理を任せられるんだ。この章で一気に解決しよう。

cn():クラスの条件付き結合
clsx と tailwind-merge を組み合わせた cn() 関数は、条件付きクラス指定と競合解決を同時に行えます。
npm install clsx tailwind-merge// lib/utils.ts
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'
function Button({ disabled, className }: { disabled?: boolean; className?: string }) {
return (
<button
className={cn(
'px-4 py-2 bg-blue-600 text-white rounded-lg font-semibold transition-colors',
disabled && 'opacity-50 cursor-not-allowed',
className,
)}
>
送信
</button>
)
}cva:バリアント管理
class-variance-authority でコンポーネントのバリアントを型安全に管理できます。
npm install class-variance-authorityimport { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const buttonVariants = cva(
// ベースクラス
'inline-flex items-center justify-center gap-2 rounded-lg font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:opacity-50 disabled:cursor-not-allowed',
{
variants: {
variant: {
primary: 'bg-blue-600 hover:bg-blue-700 text-white focus-visible:ring-blue-500',
outline: 'border border-blue-600 text-blue-600 hover:bg-blue-50 focus-visible:ring-blue-500',
ghost: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900',
danger: 'bg-red-600 hover:bg-red-700 text-white focus-visible:ring-red-500',
},
size: {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-sm',
lg: 'px-5 py-2.5 text-base',
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
}
)
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
VariantProps<typeof buttonVariants>
export function Button({ variant, size, className, ...props }: ButtonProps) {
return (
<button
className={cn(buttonVariants({ variant, size }), className)}
{...props}
/>
)
}// 使い方
<Button variant="primary" size="lg">送信</Button>
<Button variant="outline" size="sm">キャンセル</Button>
<Button variant="danger">削除</Button>動的クラスの注意点
TailwindはCSSのビルド時にHTMLやJSXをスキャンしてクラスを収集します。そのため、動的に文字列を組み立てると認識されません。
// NG:動的に組み立てると未収集になる
const color = 'blue'
<div className={`bg-${color}-600`}>NG</div>
// OK:クラス名を完全な文字列で書く
const colorMap = {
blue: 'bg-blue-600',
red: 'bg-red-600',
}
<div className={colorMap[color]}>OK</div>shadcn/ui との組み合わせ
shadcn/ui はTailwind + Radix UIをベースにしたコンポーネントライブラリです。コンポーネントのソースコードを直接プロジェクトに追加するため、自由にカスタマイズできます。
npx shadcn@latest init
npx shadcn@latest add button card dialogcn() と cva を内部で使っており、本章の知識がそのまま活きます。
ちゃんと使うためのポイント
cn()はTailwindをReactで使うなら必ず導入する- バリアントが増えたら
cvaでまとめて管理する - クラス名は完全な文字列で書く(動的な文字列結合は避ける)
- shadcn/ui はカスタマイズ性が高くおすすめのコンポーネントベース
TailwindとReactを組み合わせることで、型安全かつ保守しやすいコンポーネント指向のスタイリングが実現できます。Reactそのものをさらに深めたい場合はReact入門もあわせてどうぞ。引き続きクイズでも知識を定着させましょう。