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

コンポーネントトークンと任意の値

問題

タイトトークン戦略でコンポーネントを構築する際、チームは繰り返し同じ疑問に直面します:「必要な値がトークンセットにない — 新しいトークンを追加すべきか?」

この疑問は常に発生します。ボタンのアイコン用にちょうど28pxの幅が必要です。グリッドレイアウトに 120px1fr のカラムが必要です。装飾的なグラデーションがブランドカラーに合わない特定の赤を使います。答えが常に「トークンを追加する」であれば、トークンセットはもはやタイトではなくなるまで膨らんでいき、この戦略が防ごうとしていた制約のない混乱に戻ってしまいます。

しかし、答えが常に「既存のトークンのみを使う」であれば、開発者は合わない値の使用を強いられ、視覚的に不自然なコンポーネントが生まれます。どちらの極端もうまくいきません。足りないのは、値がシステムに属するのかコンポーネントに属するのかを判断するための明確なフレームワークです。

解決方法

タイトトークン戦略はコンポーネント中心の哲学に基づいています。これは Tailwind CSS 自体のアプローチと密接に一致しています:

  1. デザイントークンはシステムの語彙を定義する — すべてのコンポーネントにわたる一貫性を確保するスペーシング、カラー、タイポグラフィスケール
  2. コンポーネントはそれらのトークンを使って構築される — 標準的で再利用可能なスペーシングとカラーのために
  3. しかし、すべてがシステムに収まるわけではない — コンポーネント固有の詳細には任意の値を使用する

重要なポイント:トークンの追加はシステムレベルの判断であり、コンポーネントレベルの判断ではありません。 特定の値が1つのコンポーネントのレイアウトや装飾に固有のものである場合、トークンセットに昇格させるのではなく、任意の値(Tailwind の w-[28px] のようなブラケット構文)として残すべきです。

任意の値を使うべき場合

個々のコンポーネントの構造的な詳細であり、システム全体のパターンではない値には、Tailwind のブラケット構文を使いましょう:

  • コンポーネント固有のサイジング — 視覚的なバランスのためにちょうど w-[28px] p-[6px] が必要な小さなアイコンボタン
  • グリッドテンプレートカラム — 1つのコンポーネントの構造を定義する grid-cols-[120px_1fr] のようなレイアウト
  • 固有のアイコン寸法 — そのコンテキスト内で光学的に揃えるために w-[18px] にサイズ調整されたアイコン
  • 一回限りの装飾的な値 — 1つのヒーローセクションだけで使われるグラデーション from-[hsl(0_60%_20%)] to-[hsl(0_60%_8%)]
  • 数学的に計算された値 — 精密なポジショニングのためのオフセット top-[calc(100%-2px)]

システムトークンを使うべき場合

共有されたデザイン判断を表す値には、プロジェクトの @theme 定義のトークンを使いましょう:

  • 標準的なコンポーネントスペーシング — カードのパディングやセクションマージンの px-hsp-sm py-vsp-xs
  • セマンティックな意味を持つカラー — ブランドとテキスト用の bg-primary text-text-muted
  • 繰り返し要素間のギャップ — グリッドや flex レイアウト用の gap-x-hsp-xs gap-y-vsp-sm
  • デザイン仕様で名前で参照される値 — デザイナーが「セクションギャップを使って」と言えば、それはトークンです

判断フレームワーク

トークンと任意の値のどちらを使うか判断する際、このデシジョンツリーを使いましょう:

状況アクション
値が複数のコンポーネントで再利用されているシステムトークンを追加する
値がセマンティックに意味がある(例:「セクションギャップ」)システムトークンを追加する
値が1つのコンポーネントに固有任意の値を使う
値が数学的に計算されたものか装飾的なもの任意の値を使う
システムの関心事か不明まず任意の値で始め、再利用された場合に後からトークンに昇格させる

トークンを追加すべきサイン

  • 複数のコンポーネントが同じ任意の値を使っている
  • デザイナーがその値を名前付きスペーシングやカラーステップとして参照している
  • その値がピクセル数ではなくセマンティックな概念を表している
  • その値を変更するとシステム全体が更新されるべきであり、1つのコンポーネントだけではない

トークンを追加すべきでないサイン

  • ちょうど1か所でしか使われていない
  • 1つのコンポーネント固有の構造的またはレイアウト的な詳細である
  • 追加してもトークンセットが煩雑になるだけで明確さが増さない
  • その値はコンポーネントのコンテキスト外では意味を持たない

デモ

システムトークンと任意の値の混合

このカードコンポーネントは、標準的なスペーシングとカラーにシステムトークンを使い、コンポーネント固有のグリッドレイアウトと装飾的なアイコンサイジングに任意の値を使っています。コメントでトークンシステムからの値と任意の値を区別しています。

コンポーネント内のシステムトークン + 任意の値

コードの中で、システムトークンはプロジェクトのスペーシング語彙に従う値の場所に現れます — 水平パディングの hsp-sm(20px)、垂直パディングの vsp-xs(8px)、ギャップの hsp-xs(12px)。任意の値はコンポーネント固有の詳細に現れます:アイコンは光学的バランスのために 18px、グリッドはサイドバー幅に 120px、統計数値は視覚的インパクトのために 22px です。これらの任意の値はこのコンポーネント内でのみ意味を持つため、システムトークンセットに属しません。

トークン昇格:ビフォー・アフター

同じ任意の値が複数のコンポーネントで繰り返し現れる場合、それをシステムトークンに昇格させるシグナルです。このデモは、頻繁に使用されるカード幅が任意の値から名前付きトークンに昇格されたプライシングレイアウトを示しています。

トークン昇格のビフォー・アフター

「Before」バージョンでは、3人の開発者が同じプライシングカードコンポーネントに対してそれぞれ微妙に異なるパディングとフォントサイズを選びました。カードは微妙に不揃いに見えます — 垂直パディングが 18px vs 14px vs 16px、価格のフォントサイズが 28px vs 32px vs 26px。チームがこのパターンがプライシング、フィーチャーカード、テスティモニアルカードにわたって繰り返されていることに気づいた後、値をシステムトークンに昇格させました:パディングに vsp-sm / hsp-sm、価格のフォントサイズに heading。これですべてのカードが自動的に一貫性を保ちます。

実際のレイアウトでのシステムトークンと任意の値の混合

このデモは、プロダクションプロジェクトの現実的なコンポーネントをシミュレートしています。システムトークンが標準的なスペーシング、カラー、タイポグラフィをすべて処理します。任意の値はコンポーネント固有のレイアウトグリッドと装飾的な詳細を処理します。

プロダクションコンポーネント:トークン + 任意の値

実際の Tailwind プロジェクトでは、このコンポーネントのクラスは次のようになります:

<!-- System tokens for standard spacing and colors -->
<section class="px-hsp-sm py-vsp-sm">
<!-- ARBITRARY for component-specific grid layout -->
<div class="grid grid-cols-[80px_1fr] gap-x-hsp-sm">
<!-- ARBITRARY for avatar dimensions -->
<div class="w-[64px] h-[64px] rounded-full" />
<div class="flex flex-col gap-y-vsp-2xs">
<!-- System tokens for tag spacing -->
<span class="px-hsp-xs py-vsp-2xs bg-success/10 text-success">Active</span>
</div>
</div>
</section>
<!-- ARBITRARY for icon button, system tokens for primary button -->
<button class="w-[32px] h-[32px] p-[6px]">Edit</button>
<button class="px-hsp-sm py-vsp-xs bg-primary text-text-inverse">View Profile</button>

AIがよくやるミス

ミス1:すべての値にトークンを作る

<!-- BAD: Polluting the token set with one-off values -->
<!-- Adding --spacing-avatar: 64px to @theme is wrong -->
<div class="w-avatar h-avatar" />

<!-- GOOD: Use arbitrary for component-specific sizes -->
<div class="w-[64px] h-[64px]" />

一回限りの値はトークンセットを煩雑にします。64px のアバターサイズはプロフィールコンポーネント内でのみ意味があります。トークンに昇格させると、他の開発者が無関係な目的に使う可能性があり、その意図が希薄化します。

ミス2:すべてに任意の値を使う

<!-- BAD: Ignoring system tokens entirely -->
<div class="p-[20px] gap-[12px] bg-[hsl(221_83%_53%)]">

<!-- GOOD: Use system tokens for shared design values -->
<div class="px-hsp-sm gap-x-hsp-xs bg-primary">

トークンセットに値が存在する場合は、常にトークンを使いましょう。任意の値はトークンセットが本当にニーズをカバーしていない場合にのみ使うべきです。

ミス3:先行的にトークンを追加する

<!-- BAD: Adding --spacing-icon-sm: 18px because "we might need it" -->
<!-- GOOD: Start with w-[18px], promote to token only if reused -->

「後から昇格」アプローチはトークンの肥大化を防ぎます。18px のアイコンサイジングが5つのコンポーネントに現れたら、それは --spacing-icon-sm: 18px をテーマに追加する明確なシグナルです。それまでは任意の値のままにしましょう。

使い分け

このコンポーネント中心のアプローチは、タイトトークン戦略で作業する際にいつでも適用できます:

  • 新しいコンポーネントの構築 — スペーシングとカラーにはシステムトークンをデフォルトとし、グリッドカラム、アイコンサイズ、装飾的な値などのレイアウト固有の詳細には任意の値を使う
  • コンポーネントコードのレビュー — 任意の値が本当に一回限りであること、共有される値にはシステムトークンが使われていることを確認する
  • トークンセットの拡張 — 同じ任意の値が3つ以上のコンポーネントに現れたら、名前付きトークンに昇格させる
  • 既存コンポーネントのリファクタリング — 既存のトークンに一致するハードコードされた値を探して置き換える

目標は、小さく意味のあるトークンセットを維持することです。すべてのトークンはシステム全体で本当に再利用可能であることによってその存在を正当化すべきです。それ以外はすべて任意の値としてコンポーネントレベルに留めます。