zudo-css

Type to search...

to open search from anywhere

タイトトークン戦略

作成2026年3月26日更新2026年3月27日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ヒーロー / 主要セクションギャップ

コード例

デフォルトをリセットする2つのアプローチ

Tailwind CSS v4でタイトトークン戦略を実現する方法は2つあります。どちらもプロジェクトのトークンのみが存在する状態を作りますが、仕組みが異なります。

アプローチA: --*: initial による明示的リセット

すべてをインポートし、@theme 内で不要なものをリセットします:

@import "tailwindcss";

@theme {
  /* Tailwindのデフォルトをすべてリセット */
  --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;

  /* この後にトークンを定義... */
}

これは動作しますが、生成されるCSSにはデフォルトテーマレイヤーの内部変数が残ります。

アプローチB: デフォルトテーマをスキップ(推奨)

必要なレイヤーのみをインポートし、デフォルトテーマを完全にスキップします:

@import "tailwindcss/preflight";
@import "tailwindcss/utilities";

@theme {
  /* リセット不要 — デフォルトテーマは読み込まれていない */

  /* トークンを直接定義... */
}

@import "tailwindcss" は3つのレイヤーのインポートと等価です:tailwindcss/preflight(ブラウザリセット)、tailwindcss/theme(全デフォルトトークン)、tailwindcss/utilities(ユーティリティクラスエンジン)。preflight + utilities のみをインポートすることで、デフォルトテーマは単純に読み込まれません。

アプローチBを推奨する理由:

  • 記述量が少ない — --*: initial リセットブロックが不要
  • CSS出力がわずかに小さい(約1KB減)— Tailwindの内部変数が出力されない
  • メンタルモデルがクリーン — ゼロにリセットするのではなく、ゼロから始める

アプローチBで失われるもの(アプローチAとの比較)

tailwindcss/theme をスキップすると、以下のTailwind内部変数が生成CSSに出力されなくなります:

変数用途必要か?
--default-transition-durationtransition ユーティリティのデフォルト durationtransition クラスを使う場合は @theme--default-transition-duration: 0.15s; を追加
--default-transition-timing-functiontransition ユーティリティのデフォルト easing必要なら --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); を追加
--default-font-familyhtmlfont-family フォールバック--font-sans を定義するか自分で font-family を設定していれば不要
--default-mono-font-familycode/pre のフォントフォールバック--font-mono を定義していれば不要
--animate-spinanimate-spin のキーフレーム定義animate-spin を使う場合は手動で追加
--ease-in-out名前付きイージングカーブ参照する場合は手動で追加
--container-*コンテナクエリの幅@container サイズユーティリティを使わなければ不要

実際には、transition-colors などの transition ユーティリティを使う場合、@theme に以下の2行を追加してください:

@theme {
  --default-transition-duration: 0.15s;
  --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);

  /* ...トークン... */
}

完全な例(アプローチB)

プロジェクトのメインCSSファイル(例:app.css)に配置します:

@import "tailwindcss/preflight";
@import "tailwindcss/utilities";

@theme {
  /* ========================================
   * プロジェクトトークンのみを定義 — 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 学習プロジェクト — フルのデフォルトスケールを使うこと自体が学習プロセスの一部である場合

詳細ガイド

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

参考リンク

Revision History