🔶

Figma Variablesをフロントエンドに同期する

に公開
2026/03/20

これはヤプリ&フラー 合同アドベントカレンダー Advent Calendar 2025の13日目の記事です。

背景

Figma Variablesという機能を使うことで、色・サイズ・タイポグラフィ等のデザイントークンを定義し、デザインファイルの統率を図ることができます。
実装者的には、Figma Variablesをコードの変数に展開できるとFigmaとの対応づけがしやすくなります。ただ、規模によっては手動でのコピーが追いつかない場合があります。そこで、今回はトークン展開の自動化の方法を紹介します。

DTCG(Design Tokens Community Group)

https://www.designtokens.org/

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で出力する

https://help.figma.com/hc/en-us/articles/15343816063383-Modes-for-variables

Figma Variablesの1機能として提供されている方法で、各CollectionごとにDTCGトークンをエクスポートできます。
Variablesテーブルを開き、エクスポートしたいCollectionを右クリックすると選択できます。
figma variablesテーブルでcollectionを選択し、ドロップダウンメニューからexport modesをクリックしようとしている

Modesが複数ある場合は各ModeでJSONが分割されてダウンロードされます。
また、1回のExport modesで1つのCollectionしかエクスポートできないため、複数Collectionがある場合手間がかかります。

b. プラグイン(TokensBrücke)で出力

FigmaプラグインのTokensBrückeを使ってもDTCGトークンへのエクスポートが可能です。
TokensBrückeでは全てのCollectionをまとめて1ファイルにエクスポートでき、各Collectionにネストした形でトークンが格納されます。

TokensBrückeのUIで、エクスポート設定とエクスポート結果が表示されている

また、Stylesのエクスポートにも対応していたり、GitHubなどのサーバにpushできたりと痒いところに手が届くイメージがあります。

ただ、Stylesも全てを出力できるわけではないため、Terrazzoが解釈できる範囲で設定しています:

  • Typography-styles → OFF
    • プロパティの複合、つまりObjectだから変換できない
  • Grid-styles → OFF
    • Tailwind themeの名前空間に相当するものがない
  • Color-styles → OFF
    • 単純にデザインファイルで定義されていなかったから
    • グラデーションはエクスポートできると思う
  • Effect-styles → ON
    • shadowが変換できるため
  • Include .value string 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を選択します。

tz initを実行し、デザイントークンのテンプレートを選択する

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

また、CSS以外でトークンを扱う場合はTypeScriptプラグインもあると便利です。

追加したい変換プラグインを選択する

2.2 Tailwindの変換設定を行う

初期化作業が終わるとterrazzo.config.mjsが作成されるため、そこに変換設定を書いていきます。
設定項目として、トークンファイル、変換先のファイル、及びTailwindのマッピング設定を書いていきます。

terrazzo.config.mjs
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つのファイルに集約されます。

terrazzo.config.mjs
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-名前空間にマッピングされます。

terrazzo.config.mjs
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に追加します。

packages.json
"scripts": {
    "tz:build": "tz build",
    ...
}

そしてnpm scriptsを実行すると、各トークンへの変換が実行されます。

pnpm run tz:build

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変数を使って、タグ要素の背景色をラベルと共に管理できるようにしています。

page.svelte
<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を参照しているため、色・影・角丸などがそれっぽいデザインで表示されています。

影付きの青いボタン、disabled状態のグレーボタン、青い成功タグ、赤い失敗タグが開発サーバのページに表示されている

感じた課題

1. Variablesの命名がそのまま展開される (※solved)

今回使用したデジタル庁のFigmaでは、Variablesの命名がPascalCaseで統一されています。これをTailwindプラグインで変換した際、Tailwindの変数もPascalCaseで展開されました。

tailwind-tokens.css
@theme {
    /* PascalCaseで生成されている */
    --color-Primitive-LightBlue-50: color(srgb 0.9411764705882353 0.9764705882352941 1);
}

CSS Variablesの命名は基本的にkebab-caseで行うため、実装者視点ではFigma Variablesもkebab-caseで統一されてほしい気持ちです。

また、PascalCaseの場合はCSS Variablesとしては問題なく使えますが、スペース等の変数名に使えない文字が入ると無効となってしまいます。

tailwind-tokens.css
@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に載せています。
ここまで読んでいただきありがとうございました。

https://github.com/9rotama/dtcg-example

Discussion