Figma Variablesをフロントエンドに同期する
これはヤプリ&フラー 合同アドベントカレンダー Advent Calendar 2025の13日目の記事です。
背景
Figma Variablesという機能を使うことで、色・サイズ・タイポグラフィ等のデザイントークンを定義し、デザインファイルの統率を図ることができます。
実装者的には、Figma Variablesをコードの変数に展開できるとFigmaとの対応づけがしやすくなります。ただ、規模によっては手動でのコピーが追いつかない場合があります。そこで、今回はトークン展開の自動化の方法を紹介します。
DTCG(Design Tokens Community Group)
DTCG(Design Tokens Community Group)はデザイントークンの仕様策定を行うW3Cのコミュニティグループで、2025.10に仕様のStableを公開しています。
DTCGが定めるスキーマでデザイントークンを管理することで、DTCGに対応しているデザインツール同士の共有や、コードベースへの適用が容易になります。
従来の方法として、Figma APIからVariablesを取得し任意のプラットフォームに変換する方法がありました。しかし、自分で実装する必要がある上、Figmaとコードの結合が強くなってしまう懸念も含んでいました。DTCGが誕生したことで、デザインツールへの依存が減り、マルチプラットフォームで使い回せるようになります。
Terrazzo
TerrazzoはDTCGを各プラットフォームのコードに変換できるCLIツールで、12/11現在
- Web(CSS, Tailwind, TS/JS等)
- iOS(Swift)
の形式に対応しています。
もともとCobaltというツール名で、他のツールと混同しやすいことと、よりツールの方向性を示せる命名のために改名したとのことです。
競合のツールとしてStyle Dictionaryがあり、そちらの方がAndroidなど出力形式は多いのですが、Tailwind v4のCSS First Configurationsに対応していることからTerrazzoを使います。
変換を試してみる
それでは実際にFigma Variablesの変換を実装していきます。
流れとしては、Figma VariablesをDTCGトークンに変換し、そのトークンを使ってTerrazzoにコードを生成させます。
1. Figma Variables → DTCGトークン
Figma VariablesをDTCGに変換するには、Figmaのexport modes機能を使う方法とプラグインを使う方法の2つがあります。
公式のexport modesがあればプラグインは不要なのでは?と思うかもですが、プラグインの方が機能が充実しているため候補としています。
また今回は、検証用のFigmaとしてデジタル庁デザインシステム デザインデータを手元にコピーして使用しています。
a. Figma export modesで出力する
Figma Variablesの1機能として提供されている方法で、各CollectionごとにDTCGトークンをエクスポートできます。
Variablesテーブルを開き、エクスポートしたいCollectionを右クリックすると選択できます。

Modesが複数ある場合は各ModeでJSONが分割されてダウンロードされます。
また、1回のExport modesで1つのCollectionしかエクスポートできないため、複数Collectionがある場合手間がかかります。
b. プラグイン(TokensBrücke)で出力
FigmaプラグインのTokensBrückeを使ってもDTCGトークンへのエクスポートが可能です。
TokensBrückeでは全てのCollectionをまとめて1ファイルにエクスポートでき、各Collectionにネストした形でトークンが格納されます。

また、Stylesのエクスポートにも対応していたり、GitHubなどのサーバにpushできたりと痒いところに手が届くイメージがあります。
ただ、Stylesも全てを出力できるわけではないため、Terrazzoが解釈できる範囲で設定しています:
- Typography-styles → OFF
- プロパティの複合、つまりObjectだから変換できない
- Grid-styles → OFF
- Tailwind themeの名前空間に相当するものがない
- Color-styles → OFF
- 単純にデザインファイルで定義されていなかったから
- グラデーションはエクスポートできると思う
- Effect-styles → ON
- shadowが変換できるため
- Include
.valuestring for aliases → ON- Terrazzoがaliasを認識できなくなった
- Use DTCG keys format → ON
- 規則として、各トークンのプロパティには
$接頭辞が必要
- 規則として、各トークンのプロパティには
ここはTerrazzoで実際に変換を試しながら調整しました。
エクスポートされたDTCGトークンを見てみると、Figmaのcollection/groupのネスト構造がそのまま反映されていることがわかります。
digital-go.tokens.json
{
"Color": {
"Color": {
"Primitive": {
"Blue": {
"50": { // "Color" collection の "Color/Primitive/Blue/50" variable に対応
"$type": "color",
"$value": "#e8f1fe",
"$description": "",
"$extensions": {
"mode": {}
}
},
"100": {
"$type": "color",
"$value": "#d9e6ff",
"$description": "",
"$extensions": {
"mode": {}
}
},
"200": {
"$type": "color",
"$value": "#c5d7fb",
"$description": "",
"$extensions": {
"mode": {}
}
},
...
}
}
}
}
}
2. DTCGトークン → Tailwind theme
2.1 Terrazzoをプロジェクトに導入する
DTCGトークンを手に入れたら、Terrazzoを導入してTailwind themeを展開していきます。
サンプルアプリにはSvelteKitを使っています。
プロジェクト作成後、@terrazzo/cliをインストールしてconfig作成とプラグイン追加を行います。
pnpm i -D @terrazzo/cli
pnpm exec tz init
Terrazzoはテンプレートとして、HIGやPrimerといった既存のデザインシステムを用意しています。
今回はデザインシステムを持っている前提のため、Noneを選択します。

変換できるプラットフォームごとにプラグインが提供されているため、欲しい分だけ選択します。
注意点として、TailwindプラグインはCSSプラグインと共に入れる必要があります。
また、CSS以外でトークンを扱う場合はTypeScriptプラグインもあると便利です。

2.2 Tailwindの変換設定を行う
初期化作業が終わるとterrazzo.config.mjsが作成されるため、そこに変換設定を書いていきます。
設定項目として、トークンファイル、変換先のファイル、及びTailwindのマッピング設定を書いていきます。
import { defineConfig } from '@terrazzo/cli';
import tailwind from '@terrazzo/plugin-tailwind';
import css from '@terrazzo/plugin-css';
import js from "@terrazzo/plugin-js";
export default defineConfig({
tokens: ['./tokens/digital-go.tokens.json'],
plugins: [
js({
js: "tokens.js",
ts: "tokens.d.ts"
}),
css({
filename: 'css-tokens.css'
}),
tailwind({
filename: 'tailwind-tokens.css',
theme: {
/** @see https://tailwindcss.com/docs/configuration#theme */
color: ['Color.Color.*'],
text: ['Typography.FontSize.*'],
"font-weight": ['Typography.FontWeight.*'],
radius: ['Size.BorderRadius.*'],
shadow: ['Effect-styles.Elevation.*']
}
})
],
...
});
トークンファイルの設定
トークンファイルは複数受け取ることができます。
ただ、変換結果は1つのファイルに集約されます。
tokens: ['./tokens/digital-go.tokens.light.json','./tokens/digital-go.tokens.dark.json'],
変換先のファイル
変換先のファイルは各プラグインのfilenameプロパティに指定します。
手動でTailwind themeを編集したい点を考慮し、変換ファイルは自動生成物として分離しておき、プロジェクト内のglobals.cssでimportする形が良いと思います。
Tailwindへのマッピング
DTCGトークンはいくらでもネストできるため、Tailwind themeのcolor-やtext-といった名前空間との対応づけが必要になります。
今回はColor.Colorプロパティ内に全ての色が入っているため、globパターンでそれらを指定することでcolor-名前空間にマッピングされます。
tailwind({
filename: 'tailwind-tokens.css',
theme: {
/** @see https://tailwindcss.com/docs/configuration#theme */
color: ['Color.Color.*'],
text: ['Typography.FontSize.*'],
"font-weight": ['Typography.FontWeight.*'],
radius: ['Size.BorderRadius.*'],
shadow: ['Effect-styles.Elevation.*']
}
})
2.3 変換してみる
設定が一通り終わったら、@terrazzo/cliでビルドを実行します。
まず、ビルドコマンドをnpm scriptsに追加します。
"scripts": {
"tz:build": "tz build",
...
}
そしてnpm scriptsを実行すると、各トークンへの変換が実行されます。
pnpm run tz:build

変換先のファイルを見ると、トークンがCSSやJSに変換されていることが確認できます。
tailwind-tokens.css
@import "tailwindcss";
@theme {
--color-Primitive-Blue-50: color(srgb 0.9098039215686274 0.9450980392156862 0.996078431372549);
--color-Primitive-Blue-100: color(srgb 0.8509803921568627 0.9019607843137255 1);
--color-Primitive-Blue-200: color(srgb 0.7725490196078432 0.8431372549019608 0.984313725490196);
...
--radius-24: 24px;
--radius-32: 32px;
--radius-Full: 9999px;
--shadow-1: 0 1px 5px 0 color(srgb 0 0 0 / 0.30196078431372547), 0 2px 8px 1px color(srgb 0 0 0 / 0.10196078431372549);
--shadow-2: 0 1px 6px 0 color(srgb 0 0 0 / 0.30196078431372547), 0 2px 12px 2px color(srgb 0 0 0 / 0.10196078431372549);
--shadow-3: 0 1px 6px 0 color(srgb 0 0 0 / 0.30196078431372547), 0 4px 16px 3px color(srgb 0 0 0 / 0.10196078431372549);
...
}
tokens.js
export const tokens = {
"Color.Color.Primitive.Blue.50": {
".": {
"colorSpace": "srgb",
"components": [
0.9098039215686274,
0.9450980392156862,
0.996078431372549
],
"alpha": 1,
"hex": "#e8f1fe"
},
},
"Color.Color.Primitive.Blue.100": {
".": {
"colorSpace": "srgb",
"components": [
0.8509803921568627,
0.9019607843137255,
1
],
"alpha": 1,
"hex": "#d9e6ff"
},
},
...
}
変換したトークンをアプリケーションで使ってみる
アプリケーションでトークンを呼び出してみます。
以下のコードではTailwind theme形式とTypeScript形式の両方を試しています。展開されたTypeScript変数を使って、タグ要素の背景色をラベルと共に管理できるようにしています。
<script lang="ts">
import { token } from './tokens';
const tags = {
succeed: {
color: token('Color.Color.Primitive.Blue.900').hex,
text: '成功'
},
fail: {
color: token('Color.Color.Primitive.Red.900').hex,
text: '失敗'
}
} as const;
</script>
<div class="p-4">
<h1 class="text-2xl font-bold mb-4">welcome to the DTCG example app</h1>
<button class="bg-Primitive-Blue-900 rounded-8 text-Neutral-White text-16 p-4 font-700 shadow-2"
>ラベル</button
>
<button
disabled
class="bg-Neutral-SolidGray-300 rounded-8 text-Neutral-SolidGray-50 text-16 p-4 font-700"
>ラベル</button
>
<div class="mt-4">
{#each Object.entries(tags) as [key, tag]}
<span
class="inline-block px-3 py-1 rounded-6 text-Neutral-White text-16 font-700 mr-2"
style="background-color: {tag.color}">{tag.text}</span
>
{/each}
</div>
</div>
開発サーバにアクセスすると、トークンを適用したUIが確認できます\( 'ω')/ウオ~
今回はデジタル庁のFigmaを参照しているため、色・影・角丸などがそれっぽいデザインで表示されています。

感じた課題
1. Variablesの命名がそのまま展開される (※solved)
今回使用したデジタル庁のFigmaでは、Variablesの命名がPascalCaseで統一されています。これをTailwindプラグインで変換した際、Tailwindの変数もPascalCaseで展開されました。
@theme {
/* PascalCaseで生成されている */
--color-Primitive-LightBlue-50: color(srgb 0.9411764705882353 0.9764705882352941 1);
}
CSS Variablesの命名は基本的にkebab-caseで行うため、実装者視点ではFigma Variablesもkebab-caseで統一されてほしい気持ちです。
また、PascalCaseの場合はCSS Variablesとしては問題なく使えますが、スペース等の変数名に使えない文字が入ると無効となってしまいます。
@theme {
/* 変数名が途中で切れている */
--color-Primitive-Light Blue-50: color(srgb 0.9411764705882353 0.9764705882352941 1);
}
2. rem単位が使えない (※solved)
Figma Variablesは純粋な数値しか管理できず、Terrazzoで変換を行うとpxが指定されます。特にフォントサイズにおいては、ブラウザの拡大設定を考慮してremが使えるようになるといいなと思います。
CSSプラグインではtransform関数で単位変換できそうですが、Tailwindも変換設定があると便利だなと思います。
(Figma側もrem対応してほしい)
@theme {
--typography-font-size-14: 14px; // 0.875rem
--typography-font-size-16: 16px; // 1rem
...
}
まとめ
今回は、Figma VariablesからDTCGを経由してTailwindに展開する方法について紹介しました。即座に大量のデザイントークンをコードベースに適用できるため、初期段階の作業や差分管理がとても楽になると感じました。
今後、デザインシステムはcoding agentのUI実装の秩序を保つ存在になると感じています。
今回の参考実装をGitHubに載せています。
ここまで読んでいただきありがとうございました。
Discussion