tokens について
tokens は、ハードコードされたデザイン値を監査し、階層型のデザイントークン体系を導入するコーディングエージェント向けスキルです。トークンの対応付け、置換、見た目の確認、結果報告を順に進めます。
主な機能
- 01色、余白、文字のハードコード値を監査
- 02プリミティブ、セマンティック、コンポーネントのトークンを整理
- 03コード内の値をトークンに置換
- 04変更前後のスクリーンショットを比較
- 05トークン一覧と未解決値を報告
活用シーン
- 01コードベース内の分散したスタイル値を統一
- 02重複するハードコード値を名前付きトークンに置換
- 03デスクトップとモバイルの表示変化を確認
- 04トークン化の進捗と残存値を記録
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dawitlabs/ ui-skills --skill tokensインストール後、Claude Code で/tokensを入力して読み込まれたか確認します。
同梱ファイルSYSTEMS.md
詳細
/tokens — デザイントークンシステム
コードベース内のハードコードされたデザイン値をすべて監査し、3層のトークンシステム(プリミティブ → セマンティック → コンポーネント)を構築または適用して、全体のハードコード値をトークンに置き換えます。
トークンのアーキテクチャと命名規則:SYSTEMS.md
プロジェクトのルートに DESIGN-BRIEF.md がある場合は、作業を始める前に読みます。ブリーフの Color System と Typography の項目には、プリミティブトークンの正確な開始点が記載されています。ゼロから導き出すのではなく、そこにあるカラーコードとフォント名をそのまま使います。
フェーズ 0 — 環境のセットアップ
0a. ソースルートを見つける
find . -type d -name "src" \
-not -path "*/node_modules/*" \
-not -path "*/.next/*" \
-not -path "*/dist/*" \
-not -path "*/.turbo/*" \
| head -1
src/ がない場合:find . -type d -name "app" -not -path "*/node_modules/*" -not -path "*/.next/*" | head -1
結果を以降 <SRC> として使います。
0b. CSS-in-JS を検出する
grep -r "styled-components\|@emotion\|@stitches\|vanilla-extract\|linaria" package.json packages/*/package.json apps/*/package.json 2>/dev/null | head -5
見つかった場合、フェーズ 1 のカラー/スペーシング検索でも .ts ファイルとテンプレートリテラルをスキャンする必要があります。必要な箇所ではフェーズ 1 のコマンドにこれらが含まれています。使用しているライブラリを記録します。
0c. 既存のトークンシステムを検出する
以下をすべて実行し、結果が返るものを記録します。
# CSS custom properties (most common)
find . -name "globals.css" -o -name "variables.css" -o -name "tokens.css" | grep -v node_modules
# Tailwind config
find . -name "tailwind.config.*" -not -path "*/node_modules/*"
# Style Dictionary / tokens.json
find . -name "tokens.json" -o -name "design-tokens.json" | grep -v node_modules
find . -type d -name "tokens" -not -path "*/node_modules/*"
# shadcn/ui pattern (CSS vars in globals.css under :root)
grep -rn ":root" . --include="*.css" -l | grep -v node_modules | head -5
# Existing token usage (CSS vars already in use)
grep -rn "var(--" "$SRC" --include="*.tsx" --include="*.css" | head -10
結果を次のいずれかのトークン配置先に対応付けます。すべての修正はここに適用します。
- CSS カスタムプロパティ →
globals.cssの:rootブロック - Tailwind
theme.extend→tailwind.config.ts - Style Dictionary →
tokens/ディレクトリ内の JSON ファイル - なし →
globals.cssを:rootブロック付きで作成します(SYSTEMS.md を参照)
0d. 現在のトークンセットを記録する
0b で見つかったトークン配置先のファイルを読みます。既存のトークン名と値をすべて一覧にします。これを基準とし、新しいトークンが既存のものと競合または重複しないようにします。
0e. 開発サーバーを確認/起動する(表示確認用)
curl -s --max-time 2 -o /dev/null -w "%{http_code}" http://localhost:3000
200 でない場合は、開発コマンドを実行して待機します。
until curl -s --max-time 2 http://localhost:3000 > /dev/null 2>&1; do sleep 1; done && echo "ready"
0f. スクリーンショット用スクリプトを作成する
プロジェクトルートに作成します(node_modules が必要です)。フェーズ 4 で削除します。
_tokens_screenshot.mjs:
import { chromium } from '@playwright/test';
import { mkdirSync } from 'fs';
const get = (f) => { const i = process.argv.indexOf(f); return i !== -1 ? process.argv[i+1] : null; };
const url = get('--url');
const output = get('--output') ?? '/tmp/tokens-screenshot.png';
const width = parseInt(get('--width') ?? '1280');
const height = parseInt(get('--height') ?? '900');
const auth = get('--auth');
if (!url) { console.error('--url required'); process.exit(1); }
mkdirSync('/tmp/tokens', { recursive: true });
const browser = await chromium.launch();
const context = await browser.newContext({
viewport: { width, height },
...(auth ? { storageState: auth } : {}),
});
const page = await context.newPage();
try {
await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
} catch {
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
}
await page.screenshot({ path: output, fullPage: true });
await browser.close();
console.log('saved:', output);
スクリーンショットを撮るたびに、画像に対して Read ツールを使います。
0g. 認証で保護された画面に対応する
ls /tmp/uiux-auth-state.json /tmp/animate-auth-state.json /tmp/copy-auth-state.json /tmp/a11y-auth-state.json 2>/dev/null | head -1
該当する画面が見つかった場合、保護されたルートへのすべてのスクリーンショット呼び出しに --auth <path> を渡します。
フェーズ 1 — トークン監査
1a. ハードコードされたカラーをすべて見つける
SRC=<your source root>
# Hex colors in TSX/JSX (inline styles, arbitrary Tailwind values)
echo "=== Hex colors in TSX ===" && \
grep -rn "#[0-9a-fA-F]\{3,8\}" "$SRC" --include="*.tsx" --include="*.jsx" \
| grep -v "//.*#[0-9a-fA-F]" | grep -v "\.md"
# Hex colors in CSS
echo "=== Hex colors in CSS ===" && \
grep -rn "#[0-9a-fA-F]\{3,8\}" "$SRC" --include="*.css" --include="*.scss" \
| grep -v "^.*:.*var(--"
# rgb/hsl inline values
echo "=== RGB/HSL values ===" && \
grep -rn "rgba\?\|hsla\?" "$SRC" --include="*.tsx" --include="*.css" | grep -v "var(--"
# Arbitrary Tailwind color values (hex)
echo "=== Arbitrary Tailwind colors (hex) ===" && \
grep -rn "\(bg\|text\|border\|ring\|shadow\|fill\|stroke\)-\[#" "$SRC" --include="*.tsx"
# Named Tailwind palette colors that should be semantic tokens
# e.g. bg-blue-500 should become bg-primary if blue-500 is the primary color
echo "=== Named Tailwind palette colors ===" && \
grep -rn "\(bg\|text\|border\|ring\|fill\|stroke\)-\(slate\|gray\|zinc\|neutral\|stone\|red\|orange\|amber\|yellow\|lime\|green\|emerald\|teal\|cyan\|sky\|blue\|indigo\|violet\|purple\|fuchsia\|pink\|rose\)-[0-9]\{2,3\}" "$SRC" --include="*.tsx" \
| grep -v "hover:\|focus:\|dark:\|disabled:" | head -40
# Note: filter hover/focus/dark variants — those are less likely to be primary brand usage
# High-frequency named colors (>5 occurrences) are strong tokenization candidates
CSS-in-JS(styled-components / emotion)が検出された場合: 以下も実行します。
# Hex colors in template literals
grep -rn "#[0-9a-fA-F]\{3,8\}" "$SRC" --include="*.ts" --include="*.tsx" \
| grep "styled\.\|css\`\|createGlobalStyle\|keyframes" | grep -v "//.*#"
# CSS property values inside template literals
grep -rn "color:\s*#\|background:\s*#\|border.*:\s*#" "$SRC" --include="*.ts" --include="*.tsx"
1b. ハードコードされたスペーシングをすべて見つける
# Off-grid pixel values in CSS (not on 4px scale)
echo "=== Off-grid spacing in CSS ===" && \
grep -rn "\b[0-9]*[13579]\b\s*px\|[0-9]\+\.\s*[0-9]\+\s*px" "$SRC" --include="*.css" \
| grep -v "border\|outline\|shadow\|translate"
# Arbitrary Tailwind spacing values
echo "=== Arbitrary Tailwind spacing ===" && \
grep -rn "\(p\|m\|gap\|space\|w\|h\|inset\|top\|left\|right\|bottom\)-\[[0-9]" "$SRC" --include="*.tsx"
# Inline style pixel values
echo "=== Inline style px values ===" && \
grep -rn "style={{" "$SRC" --include="*.tsx" | grep "[0-9]px\|[0-9]rem" | head -20
1c. ハードコードされたタイポグラフィ値をすべて見つける
# Font sizes in CSS not from scale
echo "=== Hardcoded font sizes ===" && \
grep -rn "font-size:" "$SRC" --include="*.css" | grep -v "var(--"
# Arbitrary Tailwind font sizes
echo "=== Arbitrary Tailwind font sizes ===" && \
grep -rn "text-\[[0-9]" "$SRC" --include="*.tsx"
# Font weights not from scale
echo "=== Hardcoded font weights ===" && \
grep -rn "font-weight:\s*[0-9]" "$SRC" --include="*.css" | grep -v "var(--"
# Line heights
echo "=== Hardcoded line heights ===" && \
grep -rn "line-height:\s*[0-9]" "$SRC" --include="*.css" | grep -v "var(--"
1d. ハードコードされたシャドウ、角丸、z-index をすべて見つける
# Box shadows not from token
echo "=== Hardcoded shadows ===" && \
grep -rn "box-shadow:" "$SRC" --include="*.css" | grep -v "var(--" && \
grep -rn "shadow-\[" "$SRC" --include="*.tsx"
# Border radii not from token
echo "=== Hardcoded border radii ===" && \
grep -rn "border-radius:" "$SRC" --include="*.css" | grep -v "var(--" && \
grep -rn "rounded-\[[0-9]" "$SRC" --include="*.tsx"
# Magic z-index numbers
echo "=== Magic z-index ===" && \
grep -rn "z-index:\s*[0-9]\|z-\[[0-9]" "$SRC" --include="*.css" --include="*.tsx"
1e. 重複値と不統一な値を見つける
# Same color used with different names (group by hex value to find duplicates)
grep -rh "#[0-9a-fA-F]\{3,8\}" "$SRC" --include="*.tsx" --include="*.css" \
| grep -oE "#[0-9a-fA-F]{3,8}" | sort | uniq -c | sort -rn | head -20
出現回数の多い値は、最も多く使われているハードコードカラーです。トークン化の優先度が最も高い項目です。
1f. 主要な画面のスクリーンショットをすべて撮る(変更前の状態)
各画面について、デスクトップ版とモバイル版を撮ります。スペーシングやタイポグラフィのトークン変更により、1280px では問題なく見えるモバイルレイアウトが崩れることがあります。
node _tokens_screenshot.mjs --url http://localhost:3000 --output /tmp/tokens/home-before-desktop.png
node _tokens_screenshot.mjs --url http://localhost:3000 --output /tmp/tokens/home-before-mobile.png --width 375 --height 812
見た目が特に異なる画面を 3~5 個選びます。**すべての画像を確認します。**これらを変更前の基準とします。
続行する前に、監査結果をカテゴリ別にまとめてすべて提示します。
フェーズ 2 — トークンマップ
コードを書く前にトークンマップを作成します。これは、すべてのハードコード値と提案するトークン名の対応表です。
形式:
Hardcoded value | Occurrences | Proposed token name | Layer
#3b82f6 | 23 | --color-primary | semantic
#1d4ed8 | 8 | --color-primary-dark | semantic
#f9fafb | 31 | --color-surface | semantic
13px | 4 | --spacing-3-5 | primitive
命名規則(SYSTEMS.md より):
- プリミティブトークン:
--color-{hue}-{shade}、--space-{n}、--radius-{size} - セマンティックトークン:
--color-primary、--color-surface、--color-text、--color-border - コンポーネントトークン:
--button-bg、--card-shadow(コンポーネント固有の場合のみ) - 見た目の印象で命名しない:
--blueや--dark-grayではなく、セマンティック名を使います - セマンティック名はカラー変更後も使えます。ブランドカラーが青から緑に変わっても、
--color-primaryはそのまま適切です
トークンレイヤー別に分類します。
- 新しいプリミティブトークン — システムにまだない生の値
- 新しいセマンティックトークン — プリミティブに対応する、意味に基づいたエイリアス
- 使用する既存トークン — すでに定義済みのトークンに対応する値
フェーズ 3 を始める前に、トークンマップの確認をユーザーに求めます。
フェーズ 3 — 再帰的なトークンループ
カテゴリごとに作業します:色 → 余白 → タイポグラフィ → シャドウ → 角丸 → z-index。 カテゴリごとに最大 3 回の反復です。
3a. 新しいトークンを定義する
使用箇所を置き換える前に、新しいプリミティブトークンとセマンティックトークンをすべてトークンの定義ファイルに追加します。
CSS カスタムプロパティのパターン:
/* In globals.css */
:root {
/* Primitives */
--color-blue-500: #3b82f6;
--color-blue-700: #1d4ed8;
--space-1: 4px;
--space-2: 8px;
/* Semantic */
--color-primary: var(--color-blue-500);
--color-primary-dark: var(--color-blue-700);
--color-surface: #f9fafb;
--color-text: #111827;
--color-border: #e5e7eb;
}
/* Dark mode — same semantic names, different values */
.dark {
--color-primary: var(--color-blue-400);
--color-surface: #111827;
--color-text: #f9fafb;
--color-border: #374151;
}
Tailwind のパターン:
// tailwind.config.ts
export default {
theme: {
extend: {
colors: {
primary: 'var(--color-primary)',
surface: 'var(--color-surface)',
border: 'var(--color-border)',
},
spacing: {
'3.5': 'var(--space-3-5)',
},
},
},
}
3b. コンポーネント内のハードコード値を置き換える
出現頻度の高い値から順に、ファイルごとに作業します。それぞれについて:
- ファイルを読みます
- ハードコード値をトークンに置き換えます
- ロジック、レイアウト、コンポーネント構造は変更せず、値のみを変更します
CSS の置き換え:
/* Before */
color: #3b82f6;
/* After */
color: var(--color-primary);
Tailwind クラスの置き換え:
/* Before */
<div className="bg-[#3b82f6] text-[#111827]">
/* After */
<div className="bg-primary text-text">
インラインスタイルの置き換え(最後の手段 — CSS/Tailwind への移動を優先):
/* Before */
style={{ backgroundColor: '#3b82f6' }}
/* After — move to CSS class if possible */
className="bg-primary"
/* Or if dynamic: */
style={{ backgroundColor: 'var(--color-primary)' }}
3c. カテゴリごとにスクリーンショットを撮る
sleep 3 # hot reload
node _tokens_screenshot.mjs --url http://localhost:3000 --output /tmp/tokens/home-after-<category>-desktop.png
node _tokens_screenshot.mjs --url http://localhost:3000 --output /tmp/tokens/home-after-<category>-mobile.png --width 375 --height 812
両方の画像を確認します。 2 つのビューポートで撮影した変更前のスクリーンショットと比較します。
両方のサイズで UI が同一に見える必要があります。変更があった場合:
- 色が違う → トークン値が誤っています。トークン定義を修正します
- モバイルでレイアウトがずれる → 余白/サイズのトークンがハードコード値とは異なる値に解決されています。トークン値を修正します
3d. トークンのチェックリスト
次のカテゴリに進む前に:
- このカテゴリの値がすべてトークンに置き換えられている
- 置き換え中に新たなハードコード値が追加されていない
- デスクトップの変更前/変更後のスクリーンショットが視覚的に同一である
- モバイル(375px)の変更前/変更後のスクリーンショットが視覚的に同一である — 余白/タイポグラフィのトークンによってレイアウトがずれていない
- トークン名がプリミティブ → セマンティックの階層に従っている
- ダークモードがある場合、ダークモードのブロックで、すべてのセマンティックトークンの上書きが定義されている
- 値が同じで意味が異なるトークンがない(重複を統合する)
- トークン名が見た目(
--blue)ではなく、役割(--color-primary)を表している
不合格があれば → 3b に戻ります。3 回目の反復後は、問題として記録して次に進みます。
フェーズ 4 — クリーンアップとレポート
一時スクリプトを削除します:
rm -f _tokens_screenshot.mjs
カテゴリごとに:
✓ Colors (iteration 1) — 47 hardcoded values → 8 tokens
✓ Spacing (iteration 2) — 12 hardcoded values → 4 tokens
最終サマリーテーブル:
| カテゴリ | 変更前のハードコード値 | 作成したトークン | 置き換えた値 | 反復回数 | 状態 |
|---|---|---|---|---|---|
| 色 | ... | ... | ... | ... | ✓ / ⚠ |
| 余白 | ... | ... | ... | ... | ✓ / ⚠ |
| タイポグラフィ | ... | ... | ... | ... | ✓ / ⚠ |
| シャドウ | ... | ... | ... | ... | ✓ / ⚠ |
| 角丸 | ... | ... | ... | ... | ✓ / ⚠ |
| Z-index | ... | ... | ... | ... | ✓ / ⚠ |
トークン一覧 — 定義したすべてのトークン、その値、使用回数。
解決できなかった、問題として記録したハードコード値を、理由とともにすべて列挙します。
ルール
- 置き換える前に定義します。 トークンは、コンポーネントから参照される前にトークンの保存先に存在している必要があります。
- セマンティックトークンよりプリミティブトークンを先に定義します。 まず生の値を定義し、次にセマンティックエイリアスを定義します。コンポーネントトークンは必要な場合にのみ使用します。
- 見た目ではなく役割に基づいて命名します。
--color-primaryはリブランド後も使えますが、--color-blueは使えません。 - スクリーンショットで回帰テストを行います。 変更前と変更後の見た目は完全に一致する必要があります。異なる場合、トークンの値が誤っています。
- 適切なセマンティックレイヤーかどうかは、ダークモードで確認します。
.darkの切り替えだけでコンポーネントを編集せずに色が正しく変わるなら、トークンは適切です。 - 1つの意味に1つのトークンを使います。 値と意味が同じトークンが2つある場合は、統合します。
- すべてをトークン化しないでください。
border: 1px solidの1pxはトークン化する必要はありません。テーマによって変わる値や、複数の場所で使われる値に注目します。 - カテゴリごとの反復は最大3回までです。 問題に印を付けて、次に進みます。
- ロジックは絶対に変更しません。 変更するのはデザインの値だけです。条件、コンポーネントの構造、機能は変更しません。
トークンのアーキテクチャ、命名規則、完全なコードパターンについては、SYSTEMS.md を参照してください。