メインコンテンツまでスキップ
  • Created:
  • Updated:
  • Author:
    Takeshi Takatsudo

タイトトークン戦略

問題

Tailwind CSS はデフォルトで膨大なトークンセットを提供しています。spacing スケールだけでも 00.511.522.533.54567891011121416202428323640444852566064728096 と、30以上の数値ステップがあります。これにカラー、フォントサイズ、border-radius などのカテゴリを掛け合わせると、利用可能なユーティリティの空間は膨大になります。

実際には、チームの誰もがいつでも好きな値を選べるということを意味します。ある人は p-4 を書き、別の人は p-5 を使い、さらに別の人が p-6 を選ぶ — すべて「中くらいのパディング」のつもりです。すべての値が有効なので間違いはありませんが、結果として一貫性のない、ずれていくUIが生まれます。デザインレビューはどの数値ステップが「正しい」かの議論になり、後からスペーシングをリファクタリングするにはコードベース全体に散らばった何百ものユーティリティクラスを監査する必要があります。

根本的な原因は、Tailwind のデフォルトトークンが汎用的な数値スケールであり、セマンティックなデザイン判断ではないということです。「どれくらい」は分かっても「なぜ」が分かりません。

解決方法

すべての Tailwind デフォルトを小さく意図的なセマンティックトークン(semantic token)のセットに置き換えます。Tailwind CSS v4 の @theme ディレクティブは、ワイルドカードパターンですべてのビルトイントークンをリセットし、プロジェクトが実際に必要なトークンのみを定義できるようにします。

この戦略には2つの重要なアイデアがあります:

  1. すべてをリセットする--spacing-*: initial;--color-*: initial; などのワイルドカードを使ってすべてのデフォルト値を削除します。この後、p-4bg-gray-500 のようなユーティリティはもう存在しません。使おうとするとビルドエラーになります。まさにそれが狙いです — 無効なトークンはコードレビューではなくビルド時に検出されます。

  2. セマンティックな軸を定義する — 単一の数値スペーシングスケールの代わりに、目的ごとに異なるスケールを定義します。プロダクション向けのアプローチとして、スペーシングを2つの軸に分割します:

  • hsp(horizontal spacing):インラインのギャップ、水平方向のパディング、水平方向のマージン用
  • vsp(vertical spacing):セクション間の垂直ギャップ、垂直方向のパディング、ブロックレベルのマージン用

各軸には 2xs から 2xl までの限られたスケールがあり、チームには軸ごとにちょうど7つの選択肢が与えられます。01px のユーティリティ値と合わせて、これがプロジェクトのスペーシング語彙のすべてです。

トークン一覧表

水平スペーシング(hsp)

トークン用途
hsp-2xs5pxタイトなインラインスペーシング
hsp-xs12px小さなパディング
hsp-sm20pxデフォルトの水平パディング
hsp-md40px中セクション
hsp-lg60px大セクション
hsp-xl100px特大スペーシング
hsp-2xl250pxヒーロー / フィーチャースペーシング

垂直スペーシング(vsp)

トークン用途
vsp-2xs4px最小ギャップ
vsp-xs8pxタイトなコンポーネントギャップ
vsp-sm20pxデフォルトの垂直ギャップ
vsp-md35pxセクションギャップ
vsp-lg50px大セクションギャップ
vsp-xl65pxページセクションギャップ
vsp-2xl80pxヒーロー / 主要セクションギャップ

コード例

@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通りの方法で構築しています。左のカードはタイトなセマンティックトークンアプローチを使用し、右のカードは任意の数値スペーシング値を使用しています。あらゆる値が許可されている場合にどのように不整合が生じるかをシミュレートしています。

セマンティックトークン vs 任意の値

「Semantic tokens」カードでは、すべてのセクションがトークン一覧表の値を使用しています:ヘッダー/フッターの垂直パディングに vsp-xs(8px)、水平パディングに hsp-sm(20px)、ボディの垂直パディングに vsp-sm(20px)、タグの水平パディングに hsp-xs(12px)。結果は明確なリズムを持つ視覚的に一貫したカードです。

「Arbitrary values」カードでは、3人の開発者がそれぞれ微妙に異なるパディング値を選びました — 垂直方向に 10px14px12px、水平方向に 16px24px18px。タグの内部パディングも不揃いです。全体的にカードは微妙にバランスが崩れて見えます。この不整合は実際のプロジェクトでは何十ものコンポーネントにわたって蓄積されます。

デモ:セマンティックスペーシングを使ったページレイアウト

タイトトークンを使ったページレイアウト

このレイアウトのすべてのスペーシング値は、トークン一覧表のトークンに直接対応しています。ヘッダーは vsp-xs / hsp-sm を使い、メインセクションは vsp-md / hsp-sm を使い、カードグリッドはギャップに hsp-sm を使っています。コード(または実際のプロジェクトの Tailwind クラス)を読めば、各スペーシング値がどのセマンティックスロットに対応しているかがすぐに分かります。

使い分け

適しているケース

  • 大規模チーム — 複数の開発者が同じコンポーネントに触れる場合、制約されたトークンセットがスペーシングのずれを防ぎます
  • デザインシステム駆動のプロジェクト — デザイナーがピクセル値ではなく名前付きトークンでスペーシング仕様を渡す場合
  • プロダクションアプリケーション — 視覚的な一貫性がユーザーの信頼やブランド認知に直接影響する場合
  • 長期的なコードベース — 何年も保守・リファクタリングされるプロジェクトでは、タイトなトークンセットがグローバルなスペーシング変更を容易にします(1つのトークンを更新すれば、アプリ全体が調整されます)

不要なケース

  • プロトタイプやハッカソン — 一貫性よりスピードが重要な場合
  • 小規模な個人プロジェクト — 1人の開発者が全体のコンテキストを把握している場合
  • Tailwind 学習プロジェクト — フルのデフォルトスケールを使うこと自体が学習プロセスの一部である場合

詳細ガイド

各カテゴリの詳細なトークン戦略については以下を参照してください:

参考リンク