---
title: "Tailwind Variants(tv)の使い方｜slots・compoundVariants・レスポンシブとTailwind CSS v4対応"
url: "https://www.issoh.co.jp/tech/details/3773/"
published: 2024-10-08
updated: 2026-07-20
categories: ["Tailwind CSS"]
publisher: "株式会社一創"
---

# Tailwind Variants(tv)の使い方｜slots・compoundVariants・レスポンシブとTailwind CSS v4対応

Tailwind Variantsは、Tailwind CSSのユーティリティクラスを**コンポーネント単位のバリアントAPI**としてまとめる小さなライブラリで、`tv()`関数で色・サイズ・状態などのスタイル切り替えを型安全に定義できます。最新は**v3.2.2**で、Tailwind CSS v4に対応します。なお、検索で混同されやすい`@custom-variant`／`@variant`はTailwind CSS本体（v4）のディレクティブで、このライブラリとは別物です。本記事はこの違いを含め、導入から実務で使うキーまでを一度ずつ整理します。

## まとめ：Tailwind Variantsの要点（先に結論）

- **使いどころ**：クラス文字列が条件分岐で肥大化する再利用コンポーネント（Button、Card等）。単発の要素には不要。
- **中核API**：`tv()`の`base`／`variants`／`defaultVariants`／`slots`／`compoundVariants`／`compoundSlots`／`extend`。
- **バージョン対応**：Tailwind CSS v4 → tailwind-variants v3系＋tailwind-merge v3系。Tailwind CSS v3 → tailwind-variants v0系＋tailwind-merge v2.6.0。
- **混同注意**：ライブラリの`tv()`と、Tailwind CSS v4のネイティブ`@custom-variant`／`@variant`は無関係。用途が違う。
- **上書き**：呼び出し時に`class`／`className`を渡すと、内部のtailwind-mergeが競合クラスを解決して上書きする。

## Tailwind Variantsの役割（tvが解決する課題）

Tailwindでコンポーネントを作ると、条件によってクラス文字列を組み立てる分岐がJSX側に散らばり、可読性と再利用性が落ちます。Tailwind Variantsは、この「クラスの出し分け」を宣言的な定義に外出しするライブラリです。設計はclass-variance-authority（cva）に近い発想ですが、複数要素をまとめる`slots`、レスポンシブ対応、tailwind-mergeによる競合解決を標準で備える点が異なります。特定フレームワークに依存せず、React・Vue・Solidなどで同じ書き方が使えます。

一方で、バリアントが1〜2種類しかない小規模な要素にまで導入すると、定義ファイルが増えるだけで利点が出ません。**「同じコンポーネントを色・サイズ・状態違いで何度も使い回す」場面に絞って導入する**のが実務的な判断基準です。

## セットアップとバージョン対応（v3.2.2 / tailwind-merge / Tailwind CSS v4・v3）

インストールはパッケージを追加するだけです。tailwind-mergeはv2以降**任意のpeer dependency**で、className上書きの競合解決を使う場合に別途入れます。v3.2.2のpeer指定は`tailwind-merge >= 3.0.0`のため、Tailwind CSS v4環境では次の組み合わせになります。

| Tailwind CSS | tailwind-variants | tailwind-merge |
| ------------ | ----------------- | -------------- |
| v4           | v3系（最新 v3.2.2）    | v3系（v3.0.0以上）  |
| v3           | v0系               | v2.6.0         |

```
# Tailwind CSS v4 環境
npm install tailwind-variants tailwind-merge

# 読み込み
import { tv } from 'tailwind-variants'
```

なお単純な文字列連結（競合解決なし）を行うヘルパーは、v3.2.0で`cnBase`が`cx`に置き換えられました。旧名を使っている場合は`cx`へ移行します。変動が速い箇所なので、対応バージョンの最終確認は公式のアップグレードガイドで行ってください。

## tv()の基本構成（base・variants・defaultVariants）

`tv()`には共通クラスの`base`、切り替え定義の`variants`、既定値の`defaultVariants`を渡します。戻り値の関数に選択したバリアントを渡すと、最終的なクラス文字列が返ります。

```
import { tv } from 'tailwind-variants'

const button = tv({
  base: 'rounded font-medium text-white',
  variants: {
    color: { primary: 'bg-blue-500', secondary: 'bg-gray-500' },
    size:  { sm: 'text-sm p-2', md: 'text-base p-4' },
  },
  defaultVariants: { color: 'primary', size: 'md' },
})

button()                      // primary + md（既定）
button({ color: 'secondary', size: 'sm' })
```

評価語ではなくキー名で意図が読めるため、JSX側は`className={button({ color })}`だけになり、分岐がJSXから消えます。

## slotsによる複数要素のまとめ定義

カードやモーダルのように親要素と子要素をまとめて扱う場合は`slots`を使います。スロット数に上限はなく、`variants`内でも各スロット別にクラスを指定できます。呼び出すと各スロットの関数を持つオブジェクトが返ります。

```
const card = tv({
  slots: { base: 'rounded-lg border', title: 'font-bold', body: 'text-sm' },
  variants: {
    tone: { info: { base: 'border-blue-300', title: 'text-blue-700' } },
  },
})

const { base, title, body } = card({ tone: 'info' })
// base(), title(), body() をそれぞれ className へ
```

## compoundVariants・compoundSlotsによる交差条件スタイル

「色がsecondaryかつサイズがsmのときだけ枠線を足す」のように**複数バリアントの組み合わせに依存するスタイル**は`compoundVariants`で定義します。条件に一致したときだけ`class`が追加されます。slots構成で「特定条件×特定スロット」を狙う場合は`compoundSlots`を使います。

```
const button = tv({
  base: 'rounded text-white',
  variants: {
    color: { primary: 'bg-blue-500', secondary: 'bg-gray-500' },
    size:  { sm: 'p-2', md: 'p-4' },
  },
  compoundVariants: [
    { color: 'secondary', size: 'sm', class: 'border border-gray-400' },
  ],
})
```

個々のvariantに条件分岐を書き足すと組み合わせ爆発を起こしますが、compoundVariantsに「交差条件」を集約すると定義が一箇所にまとまります。slots構成で「特定条件のときに特定スロットだけ変える」場合は`compoundSlots`を使い、対象スロットを`slots`配列で指定します。

```
const modal = tv({
  slots: { base: 'rounded', close: 'text-gray-400' },
  variants: { size: { sm: {}, lg: {} } },
  compoundSlots: [
    { slots: ['close'], size: 'lg', class: 'text-gray-600' },
  ],
})
```

## レスポンシブVariantsの設定（responsiveVariants）

ブレークポイントごとにバリアントを切り替えるには、`tv()`の第2引数で`responsiveVariants`に対象ブレークポイントを渡します。これを有効にすると、呼び出し時にバリアント値をブレークポイントのオブジェクトで指定できます。

```
const button = tv(
  {
    base: 'text-white',
    variants: { size: { sm: 'text-sm p-2', md: 'text-base p-4', lg: 'text-lg p-6' } },
  },
  { responsiveVariants: ['md', 'lg'] }
)

button({ size: { initial: 'sm', md: 'md', lg: 'lg' } })
```

生成されるレスポンシブ用クラスがビルド時に削除されないよう、対象ブレークポイントは事前に列挙しておく必要があります。JSコンフィグを使う構成では、`tailwind-variants/transformer`の`withTV`で`tailwind.config.js`をラップして検出対象に含めます。設定方式はTailwind CSSのバージョンで異なるため、最新手順は公式ドキュメントで確認してください。

## classNameによる上書きとtailwind-mergeの役割

呼び出し時に`class`／`className`を渡すと、定義済みクラスへ追記されます。このとき`p-4`と`p-2`のような競合を後勝ちで解決するのがtailwind-mergeです。tailwind-mergeを入れていないと競合クラスが両方残り、意図しないスタイルになることがあります。

```
button({ color: 'primary', class: 'p-6' })
// base/variants の padding を p-6 が上書き（tailwind-merge が競合解決）
```

既存定義を土台に別コンポーネントを派生させたい場合は`extend`で継承し、差分だけを追記します。共通のデザイントークンを1つの定義に集約し、各コンポーネントがそれを拡張する構成にすると重複が減ります。

## tailwind-variants(tv)とTailwind CSS v4の@custom-variant/@variantの違い

検索では`@custom-variant`や`@variant`でこの記事にたどり着く人がいますが、これらは**Tailwind CSS本体（v4）のディレクティブ**で、tailwind-variantsライブラリとは無関係です。名前が似ているため混同されがちなので、役割を分けて押さえてください。

| 機能                    | 正体                      | 書く場所  | 用途                          |
| --------------------- | ----------------------- | ----- | --------------------------- |
| tailwind-variants（tv） | npmライブラリ                | JS/TS | コンポーネントのバリアントAPIを型安全に定義     |
| @custom-variant       | Tailwind CSS v4のディレクティブ | CSS   | 独自バリアントを定義（v3のaddVariant後継） |
| @variant              | Tailwind CSS v4のディレクティブ | CSS   | 手書きCSSの中で既存バリアントを適用         |

`@custom-variant`は、任意のセレクタから新しいバリアント（例：`data-*`属性やテーマ用クラス）をCSS側で作るためのものです。v4以前にプラグインの`addVariant()`で行っていた定義を置き換えます。

```
/* CSS 側で独自バリアントを定義 */
@custom-variant theme-midnight (&:where([data-theme="midnight"] *));
```

`@variant`は、手書きCSSのルール内で既存バリアント（`dark`など）を適用するためのディレクティブです。v4のalpha期に一時`@variant`という名前で定義用途を担っていた経緯があり、現在は**定義は`@custom-variant`、適用は`@variant`**と役割が分かれています。要するに、JS/TSでコンポーネントのスタイルを出し分けたいならtailwind-variants、CSSで独自の状態バリアントを増やしたいなら`@custom-variant`という使い分けです。v4全体の変更点は[Tailwind CSS v4の主な変更点と新機能の概要](https://www.issoh.co.jp/tech/details/5060/)で確認できます。

## よくある質問

### tailwind-variantsとcva（class-variance-authority）の違いは？

基本のバリアント定義は似ていますが、tailwind-variantsは複数要素をまとめる`slots`、`responsiveVariants`によるレスポンシブ対応、tailwind-mergeでの競合解決を標準で備えます。cvaはより最小構成です。

### Tailwind CSS v4で使えますか？

使えます。v4環境ではtailwind-variants v3系（最新v3.2.2）とtailwind-merge v3系を組み合わせます。Tailwind CSS v3のプロジェクトはtailwind-variants v0系とtailwind-merge v2.6.0が対応版です。

### @custom-variantとtailwind-variantsは同じものですか？

別物です。`@custom-variant`／`@variant`はTailwind CSS v4本体のCSSディレクティブ、tailwind-variantsは`tv()`を提供するnpmライブラリです。用途も書く場所（CSS／JS）も異なります。

### tailwind-mergeは必須ですか？

任意のpeer dependencyです。ただし`class`／`className`での上書きで競合クラスを後勝ちにしたい場合は必要です。入れないと競合クラスが両方残ります。

### responsiveVariantsが効かないときは？

第2引数の`responsiveVariants`に対象ブレークポイントを列挙しているか、生成されるレスポンシブ用クラスがビルド時に削除されていないかを確認します。JSコンフィグ構成では`withTV`で設定をラップします。

## 関連記事

- [Tailwind CSS v4の主な変更点と新機能の概要](https://www.issoh.co.jp/tech/details/5060/)
- [Tailwind CSSとEmotionの基本的な特徴と違いについての紹介](https://www.issoh.co.jp/tech/details/4494/)
- [daisyUIとは？Tailwind CSSコンポーネントの使い方とv5の設定変更【2026年版】](https://www.issoh.co.jp/tech/details/2987/)

---

出典: [Tailwind Variants(tv)の使い方｜slots・compoundVariants・レスポンシブとTailwind CSS v4対応](<https://www.issoh.co.jp/tech/details/3773/>)（株式会社一創）
