4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

きっかけ

Figmaのvariablesを最近やっと使い始めたのですが、

CSS変数のように色や数値に名前をつけたり、
"/" で区切るとグループで管理できたりとても便利ですよね。

これそのままCSS変数としてセットできればデザイナーとの認識違いも減って
実装がスムーズになるのでは?と思いやってみました。

結果

FigmaプラグインでVariablesをJSONとして書き出し、
JSONをもとにCSS変数ファイルを作成できます。

今回はスター数とか踏まえてこちらのプラグインでJSONを書き出しました。
Export/Import Variables

【余談】ボツ案

Tokens Studio × Style Dictionary

Variables ではなくFigmaの Tokens Studioプラグインを使って
デザイントークンの管理をするなら、ExportしたJSONを
Style DictionaryでCSSに書き出してくれるので便利です。

Githubと連携して自動化などもできるみたいなので
ガチガチにデザインシステムを実装に組み込みたいならこの方法が良さそうです。

参考

じゃあ Variables × Style Dictionary ?

Variables をJSONで書き出しして、
Style DictionaryでCSS変換すればいいのでは と思ったのですが、

Tokens Studioとは違って
JSONをStyle Dictionaryで変換できる形にしてくれるツールがないので今回は諦めました...😢

実装

細かいことは無視してとりあえずVariablesが
CSS変数として書き出せるように実装しました。

①FigmaでVariablesを作成

Figmaでローカルバリアブルを作成します。

画面収録 2024-06-16 15.56.41.gif

  • そのまま変数名になるのも意識して命名します
  • JSONを書き出せるのがコレクションごとなので、1コレクション内 で管理します

②書き出したJSONをトークン形式に変換

書き出したJSONそのままだと情報も多くわかりづらいです...
↓こんな感じ

variables.json
  "variables": [
    {
      "id": "VariableID:1:6",
      "name": "semantic/color/black",
      "description": "",
      "type": "COLOR",
      "valuesByMode": {
        "1:1": { "type": "VARIABLE_ALIAS", "id": "VariableID:1:7" }
      },
      "resolvedValuesByMode": {
        "1:1": {
          "resolvedValue": {
            "r": 0.13333334028720856,
            "g": 0.13333334028720856,
            "b": 0.13333334028720856,
            "a": 1
          },
          "alias": "VariableID:1:7",
          "aliasName": "palette/black"
        }
      },
      "scopes": ["ALL_SCOPES"],
      "hiddenFromPublishing": false,
      "codeSyntax": {}
    }
  ]

トークンライクなJSONに変換

CSSにする前に、必要な情報だけ抜き取って
いったんわかりやすい形のJSONに変換します。

スクリプト(長くなるので折りたたみ)
transformVariables.ts
import fs from 'fs';

interface Variable {
  id: string;
  name: string;
  type: string;
  resolvedValuesByMode: {
    '1:1': {
      resolvedValue: any;
      alias: string | null;
      aliasName: string | null;
    };
  };
}

interface FigmaJSON {
  id: string;
  name: string;
  modes: { [key: string]: string };
  variableIds: string[];
  variables: Variable[];
}

const inputFilePath = '.design-token/variables.json';
const outputFilePath = '.design-token/tokens.json';

// 小数点2以下は四捨五入
const roundToTwoDecimals = (value: number): number => {
  return Math.round(value * 100) / 100;
};

const setNestedProperty = (obj: any, path: string, value: any) => {
  const keys = path.split('.');
  keys.reduce((acc, key, index) => {
    if (index === keys.length - 1) {
      acc[key] = value;
      return;
    }
    if (!acc[key]) acc[key] = {};
    return acc[key];
  }, obj);
};

const convertFigmaJsonToStyleDictionary = (inputFile: string, outputFile: string) => {
  const rawData = fs.readFileSync(inputFile, 'utf8');
  const figmaJson: FigmaJSON = JSON.parse(rawData);

  const styleDictionaryTokens = figmaJson.variables.reduce((acc, variable) => {
    const { resolvedValue, aliasName } = variable.resolvedValuesByMode['1:1'];
    let value;

    if (variable.type === 'COLOR') {
      value = aliasName ? `{${aliasName.replace(/\//g, '.')}}` : `rgba(${roundToTwoDecimals(resolvedValue.r * 255)}, ${roundToTwoDecimals(resolvedValue.g * 255)}, ${roundToTwoDecimals(resolvedValue.b * 255)}, ${resolvedValue.a})`;
    } else {
      value = aliasName ? `{${aliasName.replace(/\//g, '.')}}` : roundToTwoDecimals(resolvedValue).toString();
    }

    const path = variable.name.replace(/\//g, '.');
    setNestedProperty(acc, path, { value });

    return acc;
  }, {} as { [key: string]: any });

  fs.writeFileSync(outputFile, JSON.stringify(styleDictionaryTokens, null, 2), 'utf8');
};

convertFigmaJsonToStyleDictionary(inputFilePath, outputFilePath);

変換を実行

今回は ts-node を使って npm script から実行します。

package.json
{
  "scripts": {
    "build-token": "ts-node scripts/transformVariables.ts"
  }
}
npm run build-token

スクリプトを実行したら、CSS変数を作成するためのtokens.jsonが新たに作成されます。

変換後のtokens.json

tokens.json
{
  "semantic": {
    "color": {
      "black": {
        "value": "{palette.black}"
      },
      "primary": {
        "value": "{palette.orange}"
      },
      "accent-1": {
        "value": "{palette.blue}"
      },
      "accent-2": {
        "value": "{palette.blue-green}"
      }
    },
    "content-padding": {
      "value": "{space.lg}"
    }
  },
  "palette": {
    "black": {
      "value": "rgba(34, 34, 34, 1)"
    },
    "orange": {
      "value": "rgba(255, 103, 0, 1)"
    },
    "blue": {
      "value": "rgba(73, 109, 219, 1)"
    },
    "blue-green": {
      "value": "rgba(115, 210, 222, 1)"
    }
  },
  "typo": {
    "font-size": {
      "base": {
        "value": "16"
      },
      "lg": {
        "value": "18"
      },
      "sm": {
        "value": "14"
      }
    },
    "line-height": {
      "base": {
        "value": "1.5"
      },
      "lg": {
        "value": "1.8"
      }
    }
  },
  "space": {
    "default": {
      "value": "40"
    },
    "sm": {
      "value": "32"
    },
    "lg": {
      "value": "48"
    }
  }
}

JSONファイルを見てもわかるような形に変換できました。

③JSONからCSS変数ファイルを作成

あとはこのtokens.jsonファイルをもとに、
CSS変数を定義したファイルが一発で作成されるようにします。

スクリプト(長くなるので折りたたみ)
buildVariablesCSS.ts
import fs from 'fs';

const inputFilePath = '.design-token/tokens.json';
const outputFilePath = 'src/styles/variables.css';

const generateCssVariables = (obj: any, parentKey = ''): string => {
  return Object.entries(obj).map(([key, value]) => {
    const variableName = parentKey ? `${parentKey}-${key}` : key;
    if (typeof value === 'object' && value.value === undefined) {
      return generateCssVariables(value, variableName);
    }
    const cssValue = value.value.startsWith('{') ? `var(--${value.value.slice(1, -1).replace(/\./g, '-')})` : value.value;
    return `--${variableName}: ${cssValue};`;
  }).join('\n');
};

const convertTokensJsonToCssVariables = (inputFile: string, outputFile: string) => {
  const rawData = fs.readFileSync(inputFile, 'utf8');
  const tokensJson = JSON.parse(rawData);

  const cssVariables = generateCssVariables(tokensJson);

  const cssContent = `:root {\n${cssVariables}\n}`;

  fs.writeFileSync(outputFile, cssContent, 'utf8');
};

convertTokensJsonToCssVariables(inputFilePath, outputFilePath);

全てのスクリプトを実行

先ほどの npm scriptに追加でこのスクリプトを実行します。

package.json
{
  "scripts": {
    "build-token": "ts-node scripts/transformVariables.ts && ts-node scripts/buildVariablesCSS.ts"
  }
}
npm run build-token

スクリプトを実行すると、
無事にtokens.jsonをCSS変数に変換した
src/styles/variables.cssファイルが作成されました🙌🏻

作成されたvariables.css

variables.css
:root {
--semantic-color-black: var(--palette-black);
--semantic-color-primary: var(--palette-orange);
--semantic-color-accent-1: var(--palette-blue);
--semantic-color-accent-2: var(--palette-blue-green);
--semantic-content-padding: var(--space-lg);
--palette-black: rgba(34, 34, 34, 1);
--palette-orange: rgba(255, 103, 0, 1);
--palette-blue: rgba(73, 109, 219, 1);
--palette-blue-green: rgba(115, 210, 222, 1);
--typo-font-size-base: 16;
--typo-font-size-lg: 18;
--typo-font-size-sm: 14;
--typo-line-height-base: 1.5;
--typo-line-height-lg: 1.8;
--space-default: 40;
--space-sm: 32;
--space-lg: 48;
}

感想

よい感じのプラグインがなかったので試しに自力でやってみました。

VariablesをただCSS変数にするだけなら
もうちょっと調整すればなんとかなるかも...

ただ

  • 小数点の計算
  • 単位の設定
  • 並び順

など結構あやしいところもあって制御しづらいので、あまり実用的ではないかも。


Variablesをいい感じにStyle Dictionaryで使える形に変換してくれる
プラグインが開発されたら嬉しいですね(他力本願)

参考

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?