デザイントークンはデザイナーと開発者の間で共有される語彙です。Salesforce Lightning の時代から Style Dictionary、Theo、W3C Design Token Community Group のスペックドラフトなど、さまざまな形で存在してきました。最近変わったのは、コードベースと AI コーディングエージェントの間でも共有語彙として機能するようになったことです。
color.accent = #d96a3a を知っているエージェントはその値を使います。スクリーンショットを受け取ったエージェントは「暖かいオレンジ」と推測して近いけれど違う何かを生成します。この差は数十のコンポーネントにわたって積み重なります。
figmascope はすべてのコンテキストバンドルの一部として tokens.json をエクスポートします。この記事では、フォーマットの詳細、figmascope が Figma からトークンを取得する方法、Figma Variables がないファイルの頻度推論フォールバック、そして出力を一般的なフレームワークに接続する方法を説明します。トークンが完全なエクスポートワークフローにどう組み込まれるかについては、figmascope アプリを試すか、Figma から Cursor へと Figma から Claude Code へをお読みください。
tokens.json のフォーマット
figmascope は W3C Design Token Community Group に着想を得たフォーマットを使用します。各トークンは $value と $type フィールドを持つオブジェクトで、セマンティックカテゴリキーの下にネストされます。
{
"color": {
"surface": { "$value": "#f6f2ea", "$type": "color" },
"surface-2": { "$value": "#efe9dc", "$type": "color" },
"ink": { "$value": "#1f1d1a", "$type": "color" },
"ink-muted": { "$value": "#4a4641", "$type": "color" },
"accent": { "$value": "#d96a3a", "$type": "color" },
"accent-soft": { "$value": "#f2c7a8", "$type": "color" },
"good": { "$value": "#6a8f5a", "$type": "color" },
"warn": { "$value": "#c89a3a", "$type": "color" },
"bad": { "$value": "#b8553a", "$type": "color" }
},
"spacing": {
"1": { "$value": "4px", "$type": "dimension" },
"2": { "$value": "8px", "$type": "dimension" },
"3": { "$value": "12px", "$type": "dimension" },
"4": { "$value": "16px", "$type": "dimension" },
"6": { "$value": "24px", "$type": "dimension" },
"8": { "$value": "32px", "$type": "dimension" },
"12": { "$value": "48px", "$type": "dimension" },
"16": { "$value": "64px", "$type": "dimension" }
},
"radius": {
"sm": { "$value": "4px", "$type": "dimension" },
"md": { "$value": "8px", "$type": "dimension" },
"lg": { "$value": "16px", "$type": "dimension" },
"full": { "$value": "9999px", "$type": "dimension" }
},
"typography": {
"body": {
"fontFamily": { "$value": "Inter", "$type": "fontFamily" },
"fontSize": { "$value": "14px", "$type": "dimension" },
"fontWeight": { "$value": 400, "$type": "number" },
"lineHeight": { "$value": 1.45, "$type": "number" }
},
"heading": {
"sm": {
"fontFamily": { "$value": "Inter", "$type": "fontFamily" },
"fontSize": { "$value": "18px", "$type": "dimension" },
"fontWeight": { "$value": 600, "$type": "number" },
"lineHeight": { "$value": 1.25, "$type": "number" }
}
},
"mono": {
"fontFamily": { "$value": "JetBrains Mono", "$type": "fontFamily" },
"fontSize": { "$value": "13px", "$type": "dimension" },
"fontWeight": { "$value": 400, "$type": "number" }
}
}
}
なぜ「W3C に準じた」フォーマットなのか?
W3C Design Token Community Group のスペックはまだドラフト段階です。figmascope はコアの規則($value、$type、ネストされたグループ)に従っていますが、複合型やエイリアス解決チェーンのようなエッジケースはすべて実装していません。出力は Style Dictionary、直接の JSON トラバーサル、または主要な消費者である言語モデルが利用できる十分な安定性を持っています。
使用中のトークン型
| $type | カテゴリ | $value の形式 | 例 |
|---|---|---|---|
color |
カラー | hex 文字列 | "#d96a3a" |
dimension |
スペーシング、角丸 | 単位付き CSS 文字列 | "16px" |
fontFamily |
タイポグラフィ | フォント名文字列 | "Inter" |
number |
タイポグラフィ | 単位なし数値 | 400、1.45 |
カラーは常に hex 文字列として出力されます。Figma ソースが非完全透明度の RGBA を使用している場合、アルファチャンネルは 8 桁 hex(#d96a3a80)で保持されます。
figmascope がトークンを取得する仕組み
figmascope は 2 段階の取得戦略を使います。Figma Variables が優先ソースで、頻度推論がフォールバックです。
Tier 1: Figma Variables
Figma ファイルに Variables が定義されている場合(Professional および Organization プランで利用可能な Figma のネイティブトークンシステム)、figmascope は REST API の /v1/files/:key/variables/local エンドポイントを通じてそれらを読み込みます。変数名はトークンキーとして使用され、kebab-case に正規化されます。モードは、ファイルに単一モードがある場合にはそのモードの値が直接使用される場合を除き、デフォルトモードに統合されます。
変数コレクションは tokens.json のトップレベルのカテゴリキーにマッピングされます。「Color」というコレクションは tokens.color.* を生成し、「Spacing」は tokens.spacing.* を生成します。Figma ファイルが非標準のコレクション名を使用している場合、figmascope は変数の解決された型からカテゴリを推論しようとします。
Tier 2: 頻度推論
多くの Figma ファイル — 特に古いファイルや Variables を採用していないクライアントのファイル — には変数定義がありません。figmascope はこれをノードツリー全体を走査し、塗りつぶしカラー、スペーシング値、角丸、タイポグラフィプロパティの頻度ヒストグラムを構築することで処理します。
頻度閾値を超えた値は候補トークンになります。値がどのように使用されているかから人間が読める名前を推論できない限り、カテゴリと連番インデックス(color.0、color.1…)で命名されます(例:複数のフレームにわたって背景にのみ使用されているカラーは color.surface になります)。
_meta.json マニフェストは tokensSource を "variables" または "inferred" として記録するので、エージェントプロンプトで推論が使用されたことを示すことができます。
// _meta.json — inferred fallback
{
"tokensSource": "inferred",
"warnings": [
{
"code": "tokens-inferred",
"message": "No Figma Variables found. Tokens were inferred from usage frequency."
}
]
}
推論されたトークンは有用ですが権威的ではありません。デザインシステムの仕様としてではなく、出発点として扱ってください。
レイアウト IR 内のトークン参照
スクリーンごとの IR ファイル(screens/*.json)は、生の値を埋め込む代わりに $ref 文字列を使ってパスでトークンを参照します。これにより IR がコンパクトになり、エージェントが常に単一の信頼できる情報源から値を解決できます。
{
"kind": "stack",
"name": "PrimaryButton",
"direction": "horizontal",
"gap": { "$ref": "spacing.2" },
"padding": {
"top": 10, "right": 16, "bottom": 10, "left": 16
},
"background": { "$ref": "color.accent" },
"radius": { "$ref": "radius.sm" },
"children": [
{
"kind": "leaf",
"name": "ButtonLabel",
"type": "text",
"style": { "$ref": "typography.body" },
"color": { "$ref": "color.surface" }
}
]
}
このノードを処理するエージェントは、並行する tokens.json ファイルから color.accent を #d96a3a に、spacing.2 を 8px に解決します。曖昧さなし、値のハルシネーションなし。
完全なノードスキーマのドキュメントについては per-screen IR explained をご覧ください。
Style Dictionary での tokens.json の使用
figmascope の出力は最小限の設定で Style Dictionary と互換性があります。すでに $value / $type 規則を使用しているため、Style Dictionary を tokens.json に直接向けることができます。
// style-dictionary.config.js
module.exports = {
source: ['design/tokens.json'],
platforms: {
css: {
transformGroup: 'css',
prefix: 'fs',
buildPath: 'src/styles/',
files: [{ destination: 'tokens.css', format: 'css/variables' }]
},
js: {
transformGroup: 'js',
buildPath: 'src/',
files: [{ destination: 'tokens.js', format: 'javascript/es6' }]
}
}
};
style-dictionary build を実行すると、エージェントが使用する同じソースファイルから CSS カスタムプロパティと ES モジュールエクスポートが生成されます。
Tailwind での tokens.json の使用
小さなスクリプトで tokens.json を Tailwind の theme extension に変換できます。一般的なケースでは、構造は再帰なしにトラバースできるほどフラットです。
// scripts/tokens-to-tailwind.js
const fs = require('fs');
const tokens = JSON.parse(fs.readFileSync('design/tokens.json', 'utf8'));
function flattenGroup(group) {
return Object.fromEntries(
Object.entries(group).map(([k, v]) => [k, v.$value])
);
}
const extend = {
colors: flattenGroup(tokens.color),
spacing: flattenGroup(tokens.spacing),
borderRadius: flattenGroup(tokens.radius),
};
console.log(JSON.stringify(extend, null, 2));
node scripts/tokens-to-tailwind.js > design/tailwind-extend.json
次に tailwind.config.ts で:
import extend from './design/tailwind-extend.json';
export default {
content: ['./src/**/*.{ts,tsx}'],
theme: { extend },
};
Jetpack Compose での tokens.json の使用
Android プロジェクトでは、トークン構造は Kotlin の object にきれいにマッピングされます。プログラム的に生成するか、Claude Code に依頼できます。カラートークンの場合:
// Generated from design/tokens.json
object DesignTokens {
object Color {
val surface = Color(0xFFF6F2EA)
val ink = Color(0xFF1F1D1A)
val accent = Color(0xFFD96A3A)
val accentSoft = Color(0xFFF2C7A8)
}
object Spacing {
val s1 = 4.dp
val s2 = 8.dp
val s4 = 16.dp
val s8 = 32.dp
}
}
MaterialTheme 統合を含む完全なガイドについては Jetpack Compose from Figma を参照してください。
tokens.json に含まれないもの
スコープの限界を知ることで、正確な期待値を設定できます。
- シャドウ — figmascope は現在、ドロップシャドウやインナーシャドウのトークンをエクスポートしません。シャドウ値は存在する場合、IR のノードにインラインで表示されます。
- グラデーション — グラデーション塗りつぶしは名前付きトークンとしてではなく、IR のリーフノードにそのままエクスポートされます。
- アニメーション — タイミング、イージング、トランジション値はスコープ外です。Figma のアニメーションサポートはプロトタイプモードにあり、figmascope は読み込みません。
- ブレークポイント — レスポンシブ制約は変数レベルでの Figma の概念ではなく、個別のデザイン判断が必要です。
_meta.json の warnings 配列には、きれいにエクスポートできなかった値が記録されます。バンドルをエージェントに渡す前に確認してください。
トークン名の導出方法
figmascope が Figma Variables を読み込む際、Figma 内の変数名がトークンキーになります。Color という名前のコレクション内の Colors/Surface/Primary という名前の変数は、tokens.json では tokens.color.surface.primary になります。パスセパレーターは Figma では /、JSON のネストされたキーでは . です。
Figma では、スペース、大文字、特殊文字を使った変数名が許可されます。figmascope はこれらを正規化します。
- スペースはハイフンになります:
Primary Surface→primary-surface - 大文字は小文字になります:
AccentOrange→accent-orange - 制御文字と Unicode 双方向マークは
sanitizeNameによって除去されます - キリル文字などの非ラテン文字はスラッグ互換性のためにトランスリタレーションされます
トランスリタレーションのステップは国際チームにとって重要です。Фон(背景)のようなキリル文字の変数名を使っているウクライナのデザインチームは、出力に安定した ASCII キー(fon)を得ます。これは CSS クラス名や JSON でエンコードの問題なく使用できます。Figma Variable のメタデータに存在する場合、元の名前は $description フィールドとして保持されます。
コード生成前のトークンカバレッジ確認
バンドルをエージェントに渡す前に、トークンカバレッジが適切かどうかを確認する価値があります。スクリーン IR で参照されているすべてのカラー値が tokens.json のエントリに解決されるかどうかをチェックする簡単な Node スクリプト:
// scripts/check-token-coverage.js
const fs = require('fs');
const glob = require('glob');
const tokens = JSON.parse(fs.readFileSync('design/tokens.json', 'utf8'));
const screenFiles = glob.sync('design/screens/*.json');
function getRef(obj) {
const refs = [];
if (obj && typeof obj === 'object') {
if (obj.$ref) refs.push(obj.$ref);
for (const v of Object.values(obj)) refs.push(...getRef(v));
}
return refs;
}
function resolveRef(ref, tokens) {
return ref.split('.').reduce((o, k) => o?.[k], tokens);
}
let missing = 0;
for (const file of screenFiles) {
const screen = JSON.parse(fs.readFileSync(file, 'utf8'));
for (const ref of getRef(screen)) {
if (!resolveRef(ref, tokens)) {
console.error(`Missing token: ${ref} (in ${file})`);
missing++;
}
}
}
if (missing === 0) console.log('All token refs resolve.');
else console.error(`${missing} unresolved token refs.`);
不足している参照がゼロであれば、エージェントはレイアウト IR のすべての $ref を値を作り上げることなく解決できます。不足している参照がある場合は、通常バンドルのエクスポート後に Figma ファイルが変更されたことを意味します。新しいエクスポートを取得するには figmascope を再実行してください。デザインイテレーションにわたるトークンカバレッジの維持についてのヒントは figmascope ブログでも確認できます。