きっかけ
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でローカルバリアブルを作成します。
- そのまま変数名になるのも意識して命名します
- JSONを書き出せるのがコレクションごとなので、1コレクション内 で管理します
②書き出したJSONをトークン形式に変換
書き出した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に変換します。
スクリプト(長くなるので折りたたみ)
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 から実行します。
{
"scripts": {
"build-token": "ts-node scripts/transformVariables.ts"
}
}
npm run build-token
スクリプトを実行したら、CSS変数を作成するための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変数を定義したファイルが一発で作成されるようにします。
スクリプト(長くなるので折りたたみ)
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に追加でこのスクリプトを実行します。
{
"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
: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で使える形に変換してくれる
プラグインが開発されたら嬉しいですね(他力本願)
参考