タイトトークン戦略
問題
Tailwind CSS はデフォルトで膨大なトークンセットを提供しています。spacing スケールだけでも 0、0.5、1、1.5、2、2.5、3、3.5、4、5、6、7、8、9、10、11、12、14、16、20、24、28、32、36、40、44、48、52、56、60、64、72、80、96 と、30以上の数値ステップがあります。これにカラー、フォントサイズ、border-radius などのカテゴリを掛け合わせると、利用可能なユーティリティの空間は膨大になります。
実際には、チームの誰もがいつでも好きな値を選べるということを意味します。ある人は p-4 を書き、別の人は p-5 を使い、さらに別の人が p-6 を選ぶ — すべて「中くらいのパディング」のつもりです。すべての値が有効なので間違いはありませんが、結果として一貫性のない、ずれていくUIが生まれます。デザインレビューはどの数値ステップが「正しい」かの議論になり、後からスペーシングをリファクタリングするにはコードベース全体に散らばった何百ものユーティリティクラスを監査する必要があります。
根本的な原因は、Tailwind のデフォルトトークンが汎用的な数値スケールであり、セマンティックなデザイン判断ではないということです。「どれくらい」は分かっても「なぜ」が分かりません。
解決方法
すべての Tailwind デフォルトを小さく意図的なセマンティックトークン(semantic token)のセットに置き換えます。Tailwind CSS v4 の @theme ディレクティブは、ワイルドカードパターンですべてのビルトイントークンをリセットし、プロジェクトが実際に必要なトークンのみを定義できるようにします。
この戦略には2つの重要なアイデアがあります:
-
すべてをリセットする —
--spacing-*: initial;、--color-*: initial;などのワイルドカードを使ってすべてのデフォルト値を削除します。この後、p-4やbg-gray-500のようなユーティリティはもう存在しません。使おうとするとビルドエラーになります。まさにそれが狙いです — 無効なトークンはコードレビューではなくビルド時に検出されます。 -
セマンティックな軸を定義する — 単一の数値スペーシングスケールの代わりに、目的ごとに異なるスケールを定義します。プロダクション向けのアプローチとして、スペーシングを2つの軸に分割します:
- hsp(horizontal spacing):インラインのギャップ、水平方向のパディング、水平方向のマージン用
- vsp(vertical spacing):セクション間の垂直ギャップ、垂直方向のパディング、ブロックレベルのマージン用
各軸には 2xs から 2xl までの限られたスケールがあり、チームには軸ごとにちょうど7つの選択肢が与えられます。0 と 1px のユーティリティ値と合わせて、これがプロジェクトのスペーシング語彙のすべてです。
トークン一覧表
水平スペーシング(hsp):
| トークン | 値 | 用途 |
|---|---|---|
hsp-2xs | 5px | タイトなインラインスペーシング |
hsp-xs | 12px | 小さなパディング |
hsp-sm | 20px | デフォルトの水平パディング |
hsp-md | 40px | 中セクション |
hsp-lg | 60px | 大セクション |
hsp-xl | 100px | 特大スペーシング |
hsp-2xl | 250px | ヒーロー / フィーチャースペーシング |
垂直スペーシング(vsp):
| トークン | 値 | 用途 |
|---|---|---|
vsp-2xs | 4px | 最小ギャップ |
vsp-xs | 8px | タイトなコンポーネントギャップ |
vsp-sm | 20px | デフォルトの垂直ギャップ |
vsp-md | 35px | セクションギャップ |
vsp-lg | 50px | 大セクションギャップ |
vsp-xl | 65px | ページセクションギャップ |
vsp-2xl | 80px | ヒーロー / 主要セクションギャップ |
コード例
@theme のリセットと再定義
これが戦略の核心です。プロジェクトのメインCSSファイル(例:app.css)に配置します:
@theme {
/* ========================================
* Reset ALL Tailwind defaults
* ======================================== */
--spacing-*: initial;
--color-*: initial;
--font-size-*: initial;
--font-family-*: initial;
--font-weight-*: initial;
--line-height-*: initial;
--letter-spacing-*: initial;
--border-radius-*: initial;
--shadow-*: initial;
--inset-shadow-*: initial;
--drop-shadow-*: initial;
--breakpoint-*: initial;
/* ========================================
* Define ONLY project tokens — Spacing
* ======================================== */
--spacing-0: 0;
--spacing-1px: 1px;
/* Horizontal spacing */
--spacing-hsp-2xs: 5px;
--spacing-hsp-xs: 12px;
--spacing-hsp-sm: 20px;
--spacing-hsp-md: 40px;
--spacing-hsp-lg: 60px;
--spacing-hsp-xl: 100px;
--spacing-hsp-2xl: 250px;
/* Vertical spacing */
--spacing-vsp-2xs: 4px;
--spacing-vsp-xs: 8px;
--spacing-vsp-sm: 20px;
--spacing-vsp-md: 35px;
--spacing-vsp-lg: 50px;
--spacing-vsp-xl: 65px;
--spacing-vsp-2xl: 80px;
}
この設定後:
p-4— ビルドエラー(--spacing-4トークンが存在しない)bg-gray-500— ビルドエラー(--color-gray-500トークンが存在しない)px-hsp-sm— 動作する(padding-inline: 20pxに解決される)py-vsp-md— 動作する(padding-block: 35pxに解決される)
コンポーネントでの使い方
タイトトークンセットを使うと、Tailwind のクラスは自己文書化されます。クラス名から直接意図を読み取ることができます:
<section class="px-hsp-sm py-vsp-lg">
<h1 class="pb-vsp-xs">Page Title</h1>
<p class="pb-vsp-sm">Introductory paragraph with standard vertical spacing below.</p>
<div class="flex gap-x-hsp-xs gap-y-vsp-xs">
<div class="px-hsp-xs py-vsp-2xs">Card A</div>
<div class="px-hsp-xs py-vsp-2xs">Card B</div>
</div>
</section>
すべてのスペーシング値が、その軸(水平 vs 垂直)とスケール内での相対的なサイズを伝えます。
デモ:セマンティックトークン vs 任意の値
以下のデモは、同じカードレイアウトを2通りの方法で構築しています。左のカードはタイトなセマンティックトークンアプローチを使用し、右のカードは任意の数値スペーシング値を使用しています。あらゆる値が許可されている場合にどのように不整合が生じるかをシミュレートしています。
「Semantic tokens」カードでは、すべてのセクションがトークン一覧表の値を使用しています:ヘッダー/フッターの垂直パディングに vsp-xs(8px)、水平パディングに hsp-sm(20px)、ボディの垂直パディングに vsp-sm(20px)、タグの水平パディングに hsp-xs(12px)。結果は明確なリズムを持つ視覚的に一貫したカードです。
「Arbitrary values」カードでは、3人の開発者がそれぞれ微妙に異なるパディング値を選びました — 垂直方向に 10px、14px、12px、水平方向に 16px、24px、18px。タグの内部パディングも不揃いです。全体的にカードは微妙にバランスが崩れて見えます。この不整合は実際のプロジェクトでは何十ものコンポーネントにわたって蓄積されます。
デモ:セマンティックスペーシングを使ったページレイアウト
このレイアウトのすべてのスペーシング値は、トークン一覧表のトークンに直接対応しています。ヘッダーは vsp-xs / hsp-sm を使い、メインセクションは vsp-md / hsp-sm を使い、カードグリッドはギャップに hsp-sm を使っています。コード(または実際のプロジェクトの Tailwind クラス)を読めば、各スペーシング値がどのセマンティックスロットに対応しているかがすぐに分かります。
使い分け
適しているケース
- 大規模チーム — 複数の開発者が同じコンポーネントに触れる場合、制約されたトークンセットがスペーシングのずれを防ぎます
- デザインシステム駆動のプロジェクト — デザイナーがピクセル値ではなく名前付きトークンでスペーシング仕様を渡す場合
- プロダクションアプリケーション — 視覚的な一貫性がユーザーの信頼やブランド認知に直接影響する場合
- 長期的なコードベース — 何年も保守・リファクタリングされるプロジェクトでは、タイトなトークンセットがグローバルなスペーシング変更を容易にします(1つのトークンを更新すれば、アプリ全体が調整されます)
不要なケース
- プロトタイプやハッカソン — 一貫性よりスピードが重要な場合
- 小規模な個人プロジェクト — 1人の開発者が全体のコンテキストを把握している場合
- Tailwind 学習プロジェクト — フルのデフォルトスケールを使うこと自体が学習プロセスの一部である場合
詳細ガイド
各カテゴリの詳細なトークン戦略については以下を参照してください:
- カラートークンパターン — セマンティックカラースケール、ブランドカラー、ステートカラー、サーフェスレイヤー(基盤となるアーキテクチャについては 3層カラー戦略 も参照)
- タイポグラフィトークンパターン — フォントサイズ、line-height、font-weight、letter-spacing のトークン戦略
- トークンプレビュー — 利用可能なすべてのトークンのビジュアルリファレンス
- コンポーネントトークンと任意の値 — システムトークンと任意の値の使い分け