Tailwind CSS v4で動的クラス名が使えない?CSS変数とTypeScriptで解決する2つの方法

はじめに
Tailwind CSS v4 への移行を進めていたところ、ちょっと困った問題に遭遇しました。。
今までtailwind.config.ts
で TypeScript を使って管理していた色定義が、v4 ではmain.css
に移行することになったんです。設定ファイルが CSS ベースになることで高速化の恩恵は受けられるものの、TypeScript で動的にクラス名を生成していた部分が動かなくなってしまいました。
例えば、こんな感じでmode-${props.mode}
みたいな動的なクラス名を使っていたコードが、v4 では上手く動作しなくなったんです。
概要
先に結論を書いておくと、CSS 変数を使う方法と TypeScript で色定義を管理する方法の 2 つが有効でした。個人的には CSS 変数を使う方法が Tailwind v4 の設計思想にも合っていておすすめです。
今回は、Vue3 + TypeScript 環境での実装例を交えながら、この 2 つの解決方法を詳しく説明していきます。どちらの方法も一長一短があるので、プロジェクトの要件に合わせて選択してみてください。
解決方法 1: CSS 変数を活用した動的スタイリング
CSS 変数で色を定義する
まず、main.css
で CSS 変数として色を定義します。Tailwind v4 の@theme
ディレクティブを使うことで、Tailwind のテーマシステムに統合できます。
/* main.css */
@import "tailwindcss";
@theme {
/* モード別の色定義 */
--color-mode-run: #3b82f6; /* blue-500: 実行中 */
--color-mode-stop: #eab308; /* yellow-500: 停止中 */
--color-mode-err: #ef4444; /* red-500: エラー */
}
Vue コンポーネントでの実装
Vue3 のコンポーネントでは、Tailwind の arbitrary value 機能([]
を使った任意の値の指定)と CSS 変数を組み合わせて使用します。
<!-- StatusBadge.vue -->
<template>
<span
:class="[
'px-3 py-1 rounded-full text-white font-medium transition-colors',
`bg-[var(--color-mode-${mode})]`,
`hover:bg-[var(--color-mode-${mode})]`,
]"
>
<slot />
</span>
</template>
<script setup lang="ts">
// 型安全性も確保できる
type ModeType = "run" | "stop" | "err";
const props = defineProps<{
mode: ModeType;
}>();
</script>
この方法の最大のメリットは、カラーコードの定義がmain.css
の 1 箇所に集約されることです。Tailwind v4 の設計思想である CSS ファーストのアプローチにも合致していて、とても自然な実装になります。
デモ
このデモは CodePen 上での動作確認用に簡略化したものです。実際の Vue3 + Tailwind CSS v4 プロジェクトでは以下の点が異なります:
実際のプロジェクトでの実装:
- CSS 変数は
main.css
の@theme
ディレクティブ内で定義します - Tailwind のユーティリティクラス(
bg-[var(--color-mode-${mode})]
など)を直接使用します - ビルドプロセスで Tailwind が処理され、最適化された CSS が生成されます
CodePen での制約:
- Tailwind CSS v4 のビルド環境がないため、通常の CSS で色変数を定義しています
- インラインスタイルで動作を再現していますが、実際は Tailwind のクラスを使用します
コンセプトと動作原理は同じですので、CSS 変数による動的な色の切り替えがどのように機能するかをご確認いただけます。
解決方法 2: TypeScript で色定義を管理
色定義を TypeScript ファイルで管理
2 つ目の方法は、色の定義を TypeScript ファイルで管理し、コンポーネント側で動的に適用する方法です。
// constants/colors.ts
export const MODE_COLORS = {
run: "#3B82F6", // blue-500: 実行中
stop: "#EAB308", // yellow-500: 停止中
err: "#EF4444", // red-500: エラー
} as const;
// 型定義もエクスポート
export type ModeType = keyof typeof MODE_COLORS;
コンポーネントでの使用
<!-- StatusBadge.vue -->
<template>
<!-- arbitrary valueを使う方法 -->
<span
:class="[
'px-3 py-1 rounded-full text-white font-medium',
`bg-[${modeColor}]`,
]"
>
<slot />
</span>
</template>
<script setup lang="ts">
import { computed } from "vue";
import { MODE_COLORS, type ModeType } from "@/constants/colors";
const props = defineProps<{
mode: ModeType;
}>();
const modeColor = computed(() => MODE_COLORS[props.mode]);
</script>
この方法は型安全性が完全に保証されるのが大きな利点です。IDE の補完も効きますし、誤った値を渡した場合はコンパイル時にエラーになります。ただし、色定義がmain.css
と TypeScript ファイルの 2 箇所に分散してしまうのがデメリットですね。
デモ
このデモは CodePen 上での動作確認用に JavaScript で実装したものです。実際の Vue3 + TypeScript + Tailwind CSS v4 プロジェクトでは以下の点が異なります:
実際のプロジェクトでの実装:
- 色定義は
constants/colors.ts
などの TypeScript ファイルで型定義付きで管理します MODE_COLORS
にはas const
アサーションを使い、厳密な型チェックが可能です- Tailwind の arbitrary value(
bg-[${color}]
)として動的に適用します - IDE の補完機能やリファクタリング機能が使えます
CodePen での制約:
- TypeScript が使えないため、JavaScript で実装しています
- 型安全性の恩恵は実際にはコンパイル時に得られます
- ビルドプロセスがないため、インラインスタイルで色を適用しています
TypeScript で色を一元管理し、動的に適用する基本的な考え方をご確認いただけます。実際のプロジェクトでは、ここに型安全性と IDE サポートが加わります。
まとめ
どちらを選ぶべきか?
CSS 変数方式(方法 1)がおすすめな場合
- カラーコードの定義を 1 箇所に集約したい
- Tailwind v4 の設計思想に沿った実装をしたい
- デザイナーとの協業がある(CSS ファイルの方が扱いやすい)
- ダークモード対応やテーマ切り替え機能を実装する予定がある
TypeScript 管理方式(方法 2)がおすすめな場合
- 型安全性を最優先したい
- IDE の補完機能を最大限活用したい
- 既存の TypeScript ベースの設定が多く、統一性を保ちたい
- ビルド時に色が確定し、実行時の変更は不要
個人的には、Tailwind v4 への移行という文脈ではCSS 変数を使った方法 1を推奨します。必要に応じて軽量な型定義を追加すれば、型安全性も確保できますしね。
実装時の注意点
・Tailwind のパージ対策 動的なクラス名は、Tailwind のパージ(未使用 CSS の削除)機能で削除される可能性があります。arbitrary value を使うか、safelist に追加することで対策できます。
・CSS 変数のフォールバック CSS 変数が未定義の場合のフォールバック値を設定しておくと安心です。
background-color: var(--color-mode-run, #3b82f6);
・TypeScript の型推論
as const
アサーションを使うことで、より厳密な型推論が可能になります。

最後まで読んでいただき、ありがとうございました!
この記事が少しでもお役に立てれば嬉しいです。良い開発ライフを!