はじめに
Tailwind CSS v4ではCSS-firstの設定方式が導入され、@themeディレクティブを使ってデザイントークンを定義できるようになりました。しかし、既存のCSS変数(カスタムプロパティ)を移行する際に「これは@themeに登録すべきか、それともvar()で直接参照すればいいのか」で迷うことがあります。本記事では、既存デザインシステムをTailwind v4へ段階的に移行した経験をもとに、両者の使い分け基準を整理します。
問題
デザイントークンをTailwindに移行する際、以下のようなコードに遭遇しました。
@theme {
--radius-fl-r-pill: 9999px;
}
className="rounded-fl-r-pill"
一見問題なさそうですが、これを乱用すると次のような課題が出てきます。
- 同じ値が
:rootと@themeの2箇所に定義され、二重管理になる - 値を変更する際、両方を修正しないと同期が崩れる
- 単一用途の値なのに、わざわざTailwindの命名規則に合わせて名前を付け替える手間が発生する
「Tailwindの機能だから使った方がいい」という理由だけで@themeに何でも登録すると、かえって管理コストが増えてしまいます。
解決方法
判断基準:namespaceに何個のユーティリティ族が紐づくか
Tailwindの@themeは、単なる変数定義の置き場ではなく、namespace(--color-*、--spacing-*、--radius-*など)ごとに、対応するユーティリティ族があらかじめ紐づいている仕組みです。
| namespace | 生成されるユーティリティ族 | 数 |
|---|---|---|
--color-* |
bg-* text-* border-* ring-* divide-* outline-* など |
多数 |
--spacing-* |
p-* m-* gap-* w-* h-* など |
多数 |
--radius-* |
rounded-* |
1つ |
--shadow-* |
shadow-* |
1つ |
--ease-* |
ease-* |
1つ |
つまり--color-fl-tealを1つ登録するだけで、bg-fl-tealやtext-fl-tealなど十数種類のユーティリティが一括で使えるようになります。1回の登録に対するリターンが大きいnamespaceです。
一方、--radius-*のようなnamespaceはrounded-*という単一のユーティリティ族しか生成しません。単一コンポーネント専用の値(例:pillボタン専用の9999px)をこのために@themeへ複製しても、リターンは1個のクラスにしかならず、複製コストに見合いません。
実際に生成されるユーティリティを並べてみる
言葉だけだと実感しづらいので、実際に何が生成されるかを並べてみます。
--color-fl-teal: #2ba8a2; を1行登録した場合
@theme {
--color-fl-teal: #2ba8a2;
}
| 種類 | 生成されるユーティリティ |
|---|---|
| 背景色 | bg-fl-teal |
| 文字色 | text-fl-teal |
| ボーダー色 |
border-fl-teal / border-t-fl-teal / border-r-fl-teal / border-b-fl-teal / border-l-fl-teal / border-x-fl-teal / border-y-fl-teal
|
| アウトライン色 | outline-fl-teal |
| リング色 |
ring-fl-teal / ring-offset-fl-teal
|
| divide色 | divide-fl-teal |
| box-shadow色 | shadow-fl-teal |
| caret色 | caret-fl-teal |
| accent色 | accent-fl-teal |
| fill / stroke(SVG) |
fill-fl-teal / stroke-fl-teal
|
| グラデーション |
from-fl-teal / via-fl-teal / to-fl-teal
|
1行の登録から、約20種類のユーティリティクラスが使えるようになります。
--radius-fl-r-pill: 9999px; を1行登録した場合
@theme {
--radius-fl-r-pill: 9999px;
}
| 種類 | 生成されるユーティリティ |
|---|---|
| 角丸 | rounded-fl-r-pill |
こちらは1種類のみ(方向別のrounded-t-fl-r-pillのようなものも生成されません)。
この非対称性を見ると、色namespaceは「1回登録すれば広範囲で使い回せる資産」になるのに対し、radiusのような単一用途のnamespaceは「1回登録して1箇所でしか使えない」ため、わざわざ@themeに複製するメリットが薄いことが視覚的にも分かります。
スケール設計との不整合にも注意
Tailwind標準の--radius-*は、sm md lg xl 2xl fullのような**相対的な段階(スケール)**を前提に設計されています。ここにコンポーネント固有の絶対値を混ぜると、スケールとしての一貫性が崩れます。色の場合は元々「個別の意味を持つ名前の集合」として扱われるため、この不整合が起きにくいという違いもあります。
実際の判断フロー
そのCSS変数は…
├─ 色・spacing など、多数のユーティリティ族に紐づくnamespaceに属する?
│ └─ Yes → @themeに登録する(bg-*, text-*, p-* などが一括で使える)
│
└─ radius・shadow・easingなど、単一用途・単一コンポーネント専有の値?
└─ Yes → 既存の:root変数をそのまま var() で任意値記法から参照する
// 色:@themeに登録する価値が高い
@theme {
--color-fl-teal: #2ba8a2;
}
// → bg-fl-teal / text-fl-teal / border-fl-teal ... 全部使える
// radius:単一用途なので@theme化せず既存変数を直接参照
className="rounded-[var(--fl-r-pill)]"
おわりに
Tailwind v4の@themeは強力な機能ですが、「Tailwindの新機能だから」という理由だけで全てのCSS変数を移行対象にすると、二重管理や命名の付け替えコストがかさみます。判断基準は明快で、そのnamespaceに何個のユーティリティ族が紐づいているかを見ればよいということが分かりました。
- 色・spacingなど汎用性の高いnamespace →
@themeに登録してレバレッジを効かせる - radius・shadowなど単一用途の値 → 既存の
var()をそのまま任意値記法で参照する
移行を段階的に進めるプロジェクトでは、この基準を持っておくことで「何でもTailwind化する」トラップを避けられます。
参考
JISOUのメンバー募集中!
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
興味のある方は、ぜひホームページをのぞいてみてください!
▼▼▼
https://projisou.jp