2層サイズ戦略
問題
タイトトークン戦略を使用している場合、Tailwind のデフォルトはすべてリセットされます。これには h-4、w-4、size-8 などの幅・高さユーティリティを支える数値スペーシングスケールも含まれます。アイコンのサイズ指定やカードの幅設定が必要になった瞬間、これらのクラスは機能しなくなります。
特に AI エージェントがよくやるのは、数値スペーシングトークンを再追加することです:
@theme {
/* "Just add back what we need" */
--spacing-3: 12px;
--spacing-4: 16px;
--spacing-5: 20px;
--spacing-8: 32px;
--spacing-10: 40px;
--spacing-16: 64px;
}
これでは本末転倒です。Tailwind のデフォルト数値スケールをセマンティックな意味なく再インポートしているだけです。h-4 w-4 は何も伝えません — アイコンなのか、スペーサーなのか、装飾要素なのか分かりません。タイトトークン戦略が解決しようとしていた問題に逆戻りしてしまいます。
このプロパティが特殊な理由
CSS プロパティの中には、抽象化する価値のある自然なスケールを持つものがあります:
- スペーシング(padding、margin、gap)— UI 全体で一貫したリズムがある → セマンティック軸(hsp/vsp)が理にかなう
- フォントサイズ — キャプションから見出しまで明確な階層がある → 抽象スケール(xs〜2xl)が理にかなう
- カラー — 生の値のパレットをロールにマッピングする → 3層が理にかなう
幅・高さは異なります。 16px のアイコン、40px のアバター、320px のカード、64px のサイドバートグルには共通点がありません。自然な段階もリズムも階層もありません。size-4、size-8、size-16 のような抽象スケールは、意味があるふりをした単なる任意の数値です。
解決方法
2層アプローチを使います — 抽象スケールは完全にスキップします:
| 層 | 名前 | 目的 | 例 |
|---|---|---|---|
| 1 | テーマ | ビジュアルシステムを定義するデザインレベルのサイズ | --icon-sm: 16px |
| 2 | コンポーネント | 1つのコンポーネントに固有の一回限りのサイズ | w-[28px] h-[28px] |
重要なポイント:幅・高さには Tier 0(抽象スケール)がありません。 セマンティックな名前が最初で唯一のトークンレイヤーです。それ以外はすべて任意の値です。
Tier 1: テーマトークン
サイズがデザイン判断を表す場合 — UI の構成に関する意図的な選択の場合にトークンを定義します:
@theme {
/* Icon sizes — a design system decision */
--spacing-icon-sm: 16px;
--spacing-icon-md: 20px;
--spacing-icon-lg: 24px;
/* Avatar sizes — a design system decision */
--spacing-avatar-sm: 32px;
--spacing-avatar-md: 40px;
--spacing-avatar-lg: 56px;
/* Layout sizes — an architectural decision */
--spacing-content-width: 800px;
--spacing-card-width: 300px;
}
これにより w-icon-md h-icon-md、w-avatar-sm h-avatar-sm、max-w-content-width などのユーティリティが生成されます。クラス名が自己文書化されており、そのサイズが何のためのものか一目で分かります。
トークンを作成するかどうかはアーキテクチャとデザインの判断であり、使用回数の閾値ではありません。デザインが「メインカラムは 800px」と定めているから --spacing-content-width: 800px を定義するのです — たとえ今日1つのコンポーネントしか使っていなくても。これはブランドカラーやタイプスケールの選択と同じ種類の判断です:デザインから来るのであって、何ファイルがその値を参照しているか数えて決めるものではありません。
これはアプリケーションアーキテクチャにおける「このロジックをユーティリティ関数に切り出すべきか?」の議論に似ています。答えは「3ファイルがインポートしたら」ではなく、その概念がシステム内で名前を持つに値するかどうかです。
Tier 2: 任意の値
それ以外のすべて — 一回限りのコンポーネント寸法、計算されたレイアウト、構造的な詳細 — には Tailwind のブラケット構文を使います:
<!-- Component-specific sizing -->
<button class="w-[28px] h-[28px] p-[6px]">...</button>
<!-- Grid structure -->
<div class="grid grid-cols-[240px_1fr]">...</div>
<!-- Calculated value -->
<div class="h-[calc(100vh-64px)]">...</div>
値が1つのコンポーネントの構造的な詳細(ボタンの正確なパディング、グリッドのカラムテンプレートなど)に過ぎない場合、任意の値が正しい選択です。値がシステムを定義するデザイン判断を表す場合は、現在何個のコンポーネントが使っているかに関係なく Tier 1 に属します。
デモ
間違ったアプローチ: 数値サイズの再インポート
このデモは、Tailwind の数値スペーシングスケールを幅・高さに再追加した場合に何が起こるかを示しています。クラス名には意味がありません — size-4、size-5、size-8 は何をサイズ指定しているのか伝えてくれません。
正しいアプローチ: 共有サイズにテーマトークンを使う
セマンティックなテーマトークンを使えば、クラス名が自己文書化されます。w-icon-md h-icon-md の意味はすべての開発者が分かります。アイコンサイズを変更すれば、すべてのコンポーネントが一度に更新されます。
比較: 抽象的な数値 vs セマンティックな名前
同じ UI 要素を2つの方法でサイズ指定した比較です。左側は何を意味するか分からない抽象的な数値トークン、右側はそのサイズが何のためかを正確に伝えるセマンティックなテーマトークンです。
Tier 2 の実践: 一回限りの任意の値
コンポーネント固有のサイジングには Tailwind のブラケット構文を使います。これらの値はコンポーネント内にとどめます。コンテキスト外では意味を持たないため、トークンに昇格させません。
Tailwind CSS との統合
Tailwind v4 プロジェクトでの使い方:
Tier 1 → @theme ブロックに --spacing-* プレフィックスを付けて定義します(w-* や h-* ユーティリティで使えるようにするため):
@theme {
/* Element sizing tokens — only shared, semantic sizes */
--spacing-icon-sm: 16px;
--spacing-icon-md: 20px;
--spacing-icon-lg: 24px;
--spacing-avatar-sm: 32px;
--spacing-avatar-md: 40px;
--spacing-avatar-lg: 56px;
}
使い方:w-icon-md h-icon-md、w-avatar-sm h-avatar-sm
Tier 2 → 一回限りの値には Tailwind のブラケット構文を使います:
<div class="w-[28px] h-[28px]">...</div>
<div class="w-[calc(100%-2px)]">...</div>
<div class="grid grid-cols-[240px_1fr]">...</div>
AI がよくやるミス
- 数値スペーシングスケールの再追加 —
--spacing-4: 16px、--spacing-8: 32pxなどを@themeにインポートし直すと、タイトトークン戦略が台無しになります。意味のない数値でしかありません - 抽象的なサイズトークンの作成 —
--size-sm、--size-md、--size-lgに 16px、32px、64px のような値を設定するのは汎用的すぎます。「小さいサイズ」とは何でしょうか? アイコン? アバター? ボタン? - スペーシングトークンを要素のサイジングに使用 —
hsp-smは水平方向の padding/margin 用であり、アイコンの幅用ではありません。異なる概念には異なるトークンを使います - すべてのユニークな寸法にトークンを追加 — すべてのピクセル値が名前に値するわけではありません。
top-[calc(100%-2px)]のような計算されたオフセットは構造的な詳細であり、デザイン判断ではありません - Tailwind のデフォルトを「念のため」移植 — デザイン上の理由なく数値スペーシングトークンを追加すると、タイト戦略が防ごうとしていた制約のないパレットが生まれます。トークンはデザイン判断から来るべきであり、将来の使用を見越して作るものではありません
使い分け
- タイトトークン戦略を使用しているプロジェクトで、要素のサイジング(アイコン、アバター、カード、サムネイル)が必要な場合
- AI エージェントがコンポーネントを構築する場合 — AI は一貫して
h-4 w-4を使おうとしますが、タイトトークンプロジェクトではこれは存在しません。この記事が正しいアプローチを説明しています - チームが数値サイズトークンの追加を議論している場合 — この記事がなぜ追加すべきでないかの根拠を提供します
Tailwind + コンポーネントファースト vs 一般的な CSS
2層アプローチは CSS 方法論に関係なく同じように機能しますが、Tier 2 のメカニズムが異なります:
- Tailwind + コンポーネントファースト — Tier 2 の値は JSX 内の Tailwind ブラケット構文:
w-[28px]、grid-cols-[240px_1fr]。コンポーネントファイルがスコーピングを提供します。CSS カスタムプロパティは不要です。 - 一般的な CSS(BEM、CSS Modules) — Tier 2 の値はコンポーネントスコープの CSS カスタムプロパティ:
--_button-width: 28px、--_grid-sidebar: 240px。アンダースコアプレフィックス(--_)がローカルスコープを示します。
他のトークン戦略との比較
すべての CSS プロパティが同じ数の層を必要とするわけではありません:
| プロパティ | 層数 | 理由 |
|---|---|---|
| カラー | 3(パレット → テーマ → コンポーネント) | 生の値にはパレット構造があり、抽象化する価値がある |
| フォントサイズ | 3(スケール → テーマ → コンポーネント) | キャプションから見出しまで明確な階層がある |
| スペーシング | 2(セマンティック軸 hsp/vsp) | 一貫したリズムがあり、すでにセマンティック |
| 幅・高さ | 2(テーマ → コンポーネント) | 自然なスケールがない — セマンティックな名前のみ |
関連記事:
- 3層カラー戦略 — カラーの完全な3層アーキテクチャ
- 3層フォントサイズ戦略 — フォントサイズの3層アーキテクチャ
- コンポーネントトークンと任意の値 — トークンと任意の値の使い分けに関する一般的なフレームワーク