5年前に読んだDesignTokenの命名の話
久々に読み直してみて、まとめてなかったのでまとめる
デザイントークンとは
デザイントークンは、色、フォント、スペーシングなどのビジュアルスタイルを一元管理するための仕組み
2014年に Salesforce が先駆けて導入し、現在では多くのデザインシステムで採用されている
効果的なトークン名は、デザイン、コード、ドキュメント間でチームの共通理解を向上させる
ツールでの検索・閲覧時に、意図した決定を素早く認識・想起できることが重要
デザイントークンの構造化がなぜ必要か
構造化してない世界で起きること
color: colors.black900; background: colors.gray0;
こういう実装をしていると、次の瞬間に辛くなる
- dark modeやりたい
- コントラスト基準変わった
- Primary textを少し弱くしたい
- 特定の画面だけトーン変えたい
→ 「どこで使われてるか全部探す」が地獄
構造化=「意味の層」を作ること
トークンを構造化するとは
値(raw) ↑ 意味(semantic) ↑ 用途(component)
例
// raw
black900 = "#111";
// semantic
text.primary = black900;
// component
<CardTitle color={theme.text.primary} />
コンポーネントが「色」を知らないことが重要
構造化すると何が良くなるか
① 変更の影響範囲が一気に狭くなる
「Primary text、少し薄くしたい」
- ❌ 非構造化 → 全コンポーネント修正
- ✅ 構造化 → text.primary の割当を1行変えるだけ
② dark mode / theme切替が自然に入る
text.primary = light ? black900 : gray0;
コンポーネントは一切変更なし
これが Nathan Curtis の言う token taxonomy(トークン分類学)
③ デザインと言語が一致する
デザイナーが言う:
「Primary text を使ってください」
これがコードでも
theme.text.primary
になる
black900 とか言い出さない世界
④ 意図しない再利用を防げる
raw色を直接使うと起きがち:
「このグレーいい感じだから流用しよ」
→ 本来 divider 用の色が text に混ざる
semanticを挟むと
- 「これは text.primary 用か?」
- 「divider.primary じゃない?」
という 設計上のブレーキがかかる
⑤ “将来の変更”に耐えられる
構造化してると、後からこういうことができる
- high contrast theme
- brand refresh
- 特定プロダクトだけ配色違い
- A/Bテストで配色切替
コンポーネントを壊さずに実験できる
トークン名のレベル構造
トークン名は複数の「レベル」で構成される
記事では以下の4つのグループに分類:
1. Base(基盤レベル)
トークン名のバックボーンとなる3つのレベル:
| レベル | 説明 | 例 |
|---|---|---|
| Category | 視覚的スタイルのカテゴリ | color, font, space |
| Concept | カテゴリ内の意味的なグループ | feedback, action, heading |
| Property | 適用される CSS プロパティ | background, text, border |
// Category + Property $color-background: #FFFFFF; $color-text: #000000; // Category + Concept + Property $color-feedback-background-error: #B90000; $font-heading-size-1: 64px;
2. Modifier(修飾レベル)
目的を特定するための4つの修飾レベル:
| レベル | 説明 | 例 |
|---|---|---|
| Variant | 用途による分類 | primary, secondary, success, error |
| State | インタラクティブ状態 | hover, focus, disabled |
| Scale | サイズや段階 | 1, 2, 3 / s, m, l / 100, 200 |
| Mode | 表示モード | on-light, on-dark |
// Category + Property + Variant $color-text-primary: #000000; $color-text-secondary: #666666; // Category + Concept + Property + Variant + State $color-action-background-primary-hover: #0056b3; // Category + Concept + Scale $font-heading-size-1: 64px; $font-heading-size-2: 48px;
3. Object(オブジェクトレベル)
| レベル | 説明 | 例 |
|---|---|---|
| Component | コンポーネント名 | button, input, card |
| Element | コンポーネント内の要素 | icon, label, helper-text |
| Component Group | コンポーネントグループ | forms, navigation |
// Component + Category + Property $input-color-border: #888888; // Component + Element + Category + Property $input-left-icon-color-fill: #666666; // Component Group + Category + Property $forms-color-border: #888888;
4. Namespace(名前空間レベル)
スコープを区別するための接頭辞:
| レベル | 説明 | 例 |
|---|---|---|
| System | システム名・略称 | esds-, mds-, slds- |
| Theme | テーマ名 | ocean-, courtyard- |
| Domain | ビジネスドメイン | consumer-, retail- |
// System namespace $esds-color-text-primary: #000000; $mds-font-family-serif: Georgia, serif; // Theme namespace $aads-ocean-color-primary: #0066cc; $aads-sands-color-primary: #d4a574; // Domain namespace $esds-consumer-color-marquee-text: #ffffff;
よく使われるカテゴリ
| カテゴリ | 別名 | 用途 |
|---|---|---|
color |
- | 色全般 |
font |
type, typography, text |
タイポグラフィ |
space |
units, dimension, spacing |
余白・間隔 |
size |
sizing |
サイズ |
elevation |
z-index, layer |
重なり順 |
breakpoints |
media-query, responsive |
ブレークポイント |
shadow |
depth |
影 |
time |
animation, duration |
アニメーション時間 |
スケールの種類
| タイプ | 説明 | 例 |
|---|---|---|
| Enumerated | 列挙値 | 1, 2, 3, 4, 5 |
| Ordered | 順序値(Material Design スタイル) | 50, 100, 200, ..., 900 |
| Bounded | 範囲値(HSL の明度など) | slate-42, slate-90 |
| Proportion | 比率 | 1-x, 2-x, half-x |
| T-shirt sizes | Tシャツサイズ | xs, s, m, l, xl |
設計原則
1. 同音異義語を避ける
type は typography の略としても、カテゴリの意味としても使われる曖昧な単語
text も同様で、typography、コンテンツ、プロパティと複数の解釈が可能
そのため、多くのチームは font を選択する傾向にある
2. 内部で同質性、外部で異質性
同じクラス内では一貫性を保ち、異なるクラス間では明確に区別する
例えば visualization(チャート用)と commerce(セール・在庫表示用)は、似た色でも別の concept として分離すべき
3. 柔軟性 vs 特異性のトレードオフ
// 柔軟だが曖昧 $color-success: #28a745; // 特異的で明確 $color-background-success: #28a745; $color-text-success: #155724; $color-border-success: #c3e6cb;
柔軟なトークンは様々な場面で使えるが、意図しない使い方をされる可能性がある
4. コンポーネント内から始めて昇格させる
// Step 1: コンポーネント固有のトークンとして定義 $input-color-border: #888888; // Step 2: 他のコンポーネントでも同じ値を使うことが判明 // → グローバルトークンに昇格 $forms-color-border: #888888; // Step 3: input.scss から参照を更新 // border-color: $forms-color-border;
最初からグローバルに定義するのではなく、コンポーネント固有のトークンとして始め、再利用パターンが見えてきたら昇格させる。
5. 早すぎるグローバル化を避ける
tooltip の shadow が popover や menu でも使えるかは、実際に作るまでわからない
不確実な段階でグローバルトークンを作ると、無駄な議論や名前空間の汚染につながる
実践的なトークン例
各システムの primary action hover color を比較すると、アプローチの違いが見える:
| システム | トークン名 |
|---|---|
| Bloomberg | $color-action-primary-hover |
| Salesforce | $brand-primary-active |
| Orbit | $color-product-normal-hover |
| Morningstar | $color-interactive-primary-hover |
| Infor | $ids-color-interaction-hover |
| Adobe Spectrum | $spectrum-blue-600 |
命名時の注意点
完全性
すべてのレベルを含める必要はない。必要なレベルのみを含め、意図を十分に表現できれば良い
// Good: 必要最小限 $shape-tile-corner-radius: 4px; // Bad: 冗長 $shape-tile-corner-radius-default-on-light: 4px; $shape-tile-corner-radius-default-on-dark: 4px;
順序
一般的なパターン:
- Namespace が最初(
$esds-,$mds-) - Base レベルが中央のバックボーン
- Modifier が最後(特に
modeは末尾に配置されることが多い) - Object は namespace の後、base の前
// Namespace + Object + Category + Property + Variant + State + Mode $esds-forms-color-border-error-focus-on-dark: #ff6b6b;
多階層性(Polyhierarchy)
同じ値が複数の概念に属する場合、エイリアスで対応:
// feedback の error として定義 $color-feedback-error: #B90000; // forms の error text もエイリアスとして参照 $forms-color-text-error: $color-feedback-error;