🌀

関数の引数でTailwind CSSを使ったclass名を変更しても反映されない時

2022/05/08に公開

はじめに

例えばReactで次のように書いてみるとする。

function Test() {
  return (
    <div>
      <h1 className={`text-3xl text-red-400 font-bold underline`}>
        Hello World!
      </h1>
    </div>
  )
}

この関数に引数を設けてテキストの色を変えれるようにしたい。

function Test({ textColor }) {
  return (
    <div>
      <h1 className={`text-3xl text-${textColor}-400 font-bold underline`}>
        Hello World!
      </h1>
    </div>
  )
}
<Test textColor="red" />

ところがこれだとテキストの色が反映されない。

なぜこうなるのか

公式のドキュメントに書いてあった。
https://tailwindcss.com/docs/content-configuration#class-detection-in-depth

以下一部をDeepLで翻訳しながら引用。

The way Tailwind scans your source code for classes is intentionally very simple — we don’t actually parse or execute any of your code in the language it’s written in, we just use regular expressions to extract every string that could possibly be a class name.
Tailwindがソースコードをスキャンしてクラスを探す方法は、意図的にとてもシンプルです。実際に書かれた言語でコードを解析したり実行したりすることはなく、正規表現を使ってクラス名と思われる文字列をすべて抽出するだけです。

The most important implication of how Tailwind extracts class names is that it will only find classes that exist as complete unbroken strings in your source files.
If you use string interpolation or concatenate partial class names together, Tailwind will not find them and therefore will not generate the corresponding CSS:
Tailwind がクラス名を抽出する方法の最も重要な点は、 ソースファイル中に完全な文字列として存在するクラスのみを検出することです。
もし、文字列の補間をしたり、クラス名の一部を連結したりすると、Tailwind はそれを見つけられず、対応する CSS を生成することができません。

なるほど・・・
つまり"text-${変数}-400"みたいな書き方だと完全な文字列ではないから、後から変数に"red"とか"blue"とか代入してもCSSが生成されない・・・ということでいいのかな?
説明が難しい・・・

でもなんとなく道理は分かったので対策してみる。

解決法その1

変数に完全なクラス名を代入する。

function Test({ textColor }) {

  return (
    <div>
      <h1 className={`text-3xl ${textColor} font-bold underline`}>
        Hello World!
      </h1>
    </div>
  )
}
<Test textColor="text-red-400" />

解決法その2

関数内で完全なクラス名を作成して、それをクラスに代入する

function Test({ textColor }) {

  const textColorString = "text-" + textColor + "-400";
  return (
    <div>
      <h1 className={`text-3xl ${textColorString} font-bold underline`}>
        Hello World!
      </h1>
    </div>
  )
}
<Test textColor="red" />

番外編

tailwind.config.jsにsafelistを追加すると、最初に書いたコードでも動く。

module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{js,jsx}",
  ],
  safelist: [
    'text-red-400',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

でも公式ではあまり推奨されないらしい。
https://tailwindcss.com/docs/content-configuration#safelisting-classes

またDeepL使いながら一部引用。

For the smallest file size and best development experience, we highly recommend relying on your content configuration to tell Tailwind which classes to generate as much as possible.
Safelisting is a last-resort, and should only be used in situations where it’s impossible to scan certain content for class names. These situations are rare, and you should almost never need this feature.
If you need to make sure Tailwind generates certain class names that don’t exist in your content files, use the safelist option:
最小のファイルサイズで最高の開発体験を得るためには、 Tailwind が生成するクラスをできるだけコンテンツの設定に依存することを強くお勧めします。
セーフリスト機能は最後の手段であり、特定のコンテンツをスキャンしてクラス名を調べることが不可能な場合にのみ使用すべきです。このような状況は稀であり、この機能が必要になることはほとんどないはずです。
もし、Tailwind がコンテンツファイルに存在しない特定のクラス名を生成することを確認する必要がある場合は、セーフリスト・オプションを使用してください。

個人的にも一々safelistに書き足していくのは面倒だなと思うので、↑の解決法のどれかを採用する方向で行こうかなと思う。

Discussion