できない.dev

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 系統で解決する。

よくある原因

  1. テンプレートリテラル: `text-${size}` のように一部だけ変数化している。
    完全な text-lg の連続文字列がソース中に出てこない。
  2. 三項演算子 / props 埋め込み: `text-${isActive ? 'red' : 'blue'}-500` も同様で、完全な text-red-500 がコード上に現れない。
    親から受け取った props を `bg-${prop}` で展開する書き方も同じ罠。
  3. 配列 / map 生成: 色配列をループで回してクラス名を組み立てるパターン。
    実行時に生成される文字列はビルド時には存在しない。
  4. ビルド後の 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 経由にする発想。

この記事は役立ちましたか?