Tailwind CSS で動的に組み立てたクラス名が反映されない
Tailwind の JIT スキャナはソース上の完全な文字列だけをクラス候補に拾う。
`bg-${color}-500` のような実行時補完は検出されず CSS に出力されない。
完全文字列を分岐で書く、safelist に登録する、arbitrary value で逃がす、のいずれかで解決する。
#tailwindcss#dynamic#safelist#content#jit
公開:
要約
Tailwind の JIT スキャナは ソースコード上の完全な文字列だけ をクラス候補として抽出する。
テンプレートリテラルで組み立てた `bg-${color}-500` のような文字列は検出されず、最終 CSS にクラスが含まれない。
完全文字列で書く・safelist に入れる・arbitrary value で CSS 変数を渡す、の 3 系統で解決する。
よくある原因
- テンプレートリテラル:
`text-${size}`のように一部だけ変数化している。
完全なtext-lgの連続文字列がソース中に出てこない。 - 三項演算子 / props 埋め込み:
`text-${isActive ? 'red' : 'blue'}-500`も同様で、完全なtext-red-500がコード上に現れない。
親から受け取った props を`bg-${prop}`で展開する書き方も同じ罠。 - 配列 / map 生成: 色配列をループで回してクラス名を組み立てるパターン。
実行時に生成される文字列はビルド時には存在しない。 - ビルド後の CSS に無い: 結果として最終 CSS に対象クラスが出力されておらず、ブラウザに当てるべき値が無い。
解決策
1. 完全文字列で書く(最も推奨)
// NG: スキャンで検出されない
<div className={`bg-${color}-500`}>...</div>
// OK: 完全文字列を map に並べる
const bgMap = {
red: "bg-red-500",
blue: "bg-blue-500",
} as const;
<div className={bgMap[color]}>...</div>公式の dynamic class names ガイド でも、変数を文字列補間で組み立てず 完全クラス名を分岐させる ことが第一の推奨と明記されている。
2. safelist に登録する
// tailwind.config.js
module.exports = {
content: ["./src/**/*.{ts,tsx}"],
safelist: [
"bg-red-500",
"bg-blue-500",
{ pattern: /bg-(red|blue|green)-(100|500|900)/ },
],
};外部データに依存して静的解析に乗せられない場合は safelist を使う。pattern で正規表現指定も可能で、グループ単位でまとめて登録できる。
3. arbitrary value で CSS 変数を渡す
<div
style={{ "--brand": color } as React.CSSProperties}
className="bg-[var(--brand)]"
>
...
</div>色やサイズが完全に実行時依存(テーマカラーの動的選択など)なら、CSS 変数を介して Tailwind の任意値構文 bg-[var(--brand)] で受け取るのが自然。
クラス名の組み立てを諦めて style 経由にする発想。