Figma-tokensからJSON export→CSS variables化すると一部スタイルが正しく吐き出せない問題を解決する
この記事は、
の記事です。
株式会社エアークローゼットでエンジニアをしている小林です。
この記事ではFigma-Tokens + Style Dictionaryでデザイントークンをコードと連携するときのハマりどころについて書きます。
エアークローゼット社内では、デザイナーとエンジニアの協業を推し進める動きが今年はじめごろから活発化しはじめました。Figmaの導入を皮切りに、様々な作業フェーズで作業効率や成果物の質を高めるような試みが生まれています。
導入コストや浸透させるための取り組みなど、Figma導入に関するノウハウについては、昨日のアドベントカレンダーでかなり詳しく書かれていますので、興味があればぜひご覧ください!
自分はエンジニアとしてこの取り組みに携わっており、今後はデザイントークンとコードの連携や事後運用を見据えた設計を担当していく予定です。
今回の記事ではデザイントークンのコード連携時に行き当たった問題について調査した内容を、備忘録も兼ねて残そうと思います。
Figma-Tokens + Style Dictionaryの連携方法自体はすでにわかりやすくまとまっている記事があるため、そちらをご覧ください!
summary
- Figma-TokensからJSON形式のconfigファイルをexportすると、ネストされたObjectはそのままStyle DictionaryでCSS variables化できない
- Figma-Tokensが提供するパーサライブラリを使って、Style Dictionaryが解読可能な形式になるようにフォーマットする必要あり(2022/12/08時点)
- 一部スタイル(dropShadowなど)はネスト階層がさらに深いためデフォルトでは分解しきれない
-
--expandShadow
など対応するoptionを指定することで解決可能
-
Figma-TokensがexportするJSONの形式と課題
Figma-TokensからexportされるJSONは、そのままでも一応Style Dictionaryで読み込むことができる形式になっています。
以下画像のように定義されたcolorsは、JSONで以下のように表現されます(公式ドキュメントよりJSONを引っ張ってきました)。
{
"config": {
"colors": {
"red": {
"value": "#FF0000",
"type": "color"
},
"blue": {
"value": "#0000ff",
"type": "color"
}
}
}
}
これをそのままstyle-dictionaryでbuildした場合、以下のようになります。
:root {
--colors-red: #FF0000;
--colors-blue: #0000ff;
}
--tokenName
-propertyName
: propertyName.value
の関係が成り立っていることがわかります。
Design tokens are the platform-agnostic way to define the input for Style Dictionary. A design token is a collection of attributes that describe any fundamental/atomic visual style. Each attribute is a key-value pair.
と公式ドキュメントにあるように、key-value
のペアによってトークンの定義が成り立っており、それを見つけるまでネストを掘ったのち、構造に合わせてフォーマットされているようです。
これを踏まえて、dropShadowのデザイントークンを作成して、そのままbuildしてみます。
"shadow": {
"value": {
"x": "5",
"y": "5",
"blur": "20",
"spread": "20",
"color": "rgba(0,0,0,0.25)",
"type": "dropShadow"
},
"type": "boxShadow"
}
--shadow: [object Object];
[object Object]で出力されてしまいます。このままでは使えないですね。
そもそもドキュメントを見る感じ、Figma-TokensでexportするJSONはあくまでFigma-Tokensのナマの設定ファイルであり、
- Figma-Tokens由来のエイリアスや計算式
- [object Object]のようになってしまうような複雑なObjectのネスト
などが残っている状態みたいですね。
style DictionaryにJSONを正しく読ませるための「token-transformer」
この問題の解決には、Figma-Tokensが提供するtoken-transformerを使います。npm packagesのdocsリンクはFigma-tokens repositoryのリンクが貼ってあるんですが、実際にいろいろ書いてあるのは↓なのでこちらを見たほうが情報が見つかるかも
Figma-Tokens由来のエイリアスや計算式をパースするとともに、optionで様々なフォーマット指定ができます。[object Object]になったりするような状態の解消はだいたいoption指定でなんとかできるみたいです。
今回例に出した dropShadow
属性の展開には--expandShadow
の指定が必須でした。 このへんドキュメントにもissueにも明示的に書いてなくてちょっとつらかった。
以下、transformの実行とStyle Dictionaryでbuildしてみた結果です。
$ npm i -D token-transformer
# $ token-transformer {file path of targer JSON} {output path}
$ token-transformer ./src/configs/design-tokens.json ./src/configs/formatted-design-tokens.json --expandShadow
"shadow": {
- "value": {
- "x": "5",
- "y": "5",
- "blur": "20",
- "spread": "20",
- "color": "rgba(0,0,0,0.25)",
- "type": "dropShadow"
- },
- "type": "boxShadow"
+ "x": {
+ "value": "5",
+ "type": "x"
+ },
+ "y": {
+ "value": "5",
+ "type": "y"
+ },
+ "blur": {
+ "value": 20,
+ "type": "blur"
+ },
+ "spread": {
+ "value": 20,
+ "type": "spread"
+ },
+ "color": {
+ "value": "#rgba(0,0,0,0.25)",
+ "type": "color"
+ },
+ "type": {
+ "value": "dropShadow",
+ "type": "type"
}
},
:root {
--shadow-x: 5;
--shadow-y: 5;
--shadow-blur: 20;
--shadow-spread: 20;
--shadow-color: #rgba(0,0,0,0.25);
--shadow-type: dropShadow;
}
値ごとに分割することができました。実際にdrop-shadow属性を指定するときはこれらを組み合わせて使うことになるでしょう。
おわりに
デザイントークンを導入するための事前調査として少し手を動かしてみて、
- colorやspacingといったシンプルなトークンは比較的かんたんに連携できる
- 複数の要素を組み合わせないとCSS側で再現できない表現についてはカスタマイズが必要
であることがわかりました。このあたりをメンテナブルに保つ設計が求められそうです。
また、このような処理を挟まないといけないということは、現状Figma-tokensだけではデザイントークンとコードの厳密な対応をとることができない 、ということでもあります。デザイナーとエンジニア間の共通言語として運用する上で、この部分は小さくない課題になるだろうと感じました。
うまくまとまるように吐き出してほしいところですが、これに関してはStyle Dictionary側の設定などを駆使していくことにします。
解のひとつとして、Style Dictionary側のformat設定をつかってdropShadowの値をひとつにまとめるやり方を思いつきました。expandShadowオプションは使いません。 ……この記事意味なくなったかも?
まったくまとまってないですが、以下にサンプルを置いておきますので、ご参考までに!
StyleDictionary.registerTransform({
type: "name",
name: "sample",
matcher: (t) => {
return t.path.includes("shadow");
},
transformer: (shadowToken) => {
const keyName = "dropShadow-filter";
const { x, y, blur, spread, color } = shadowToken.value;
shadowToken.value = `drop-shadow(${x}px ${y}px ${blur}px ${color})`;
return keyName;
},
});
const sd = StyleDictionary.extend({
source: ["src/configs/design-tokens/formatted-token.json"],
platforms: {
css: {
transformGroup: "css",
buildPath: "src/assets/css/",
files: [
{
destination: "_variables.css",
format: "css/variables",
},
],
transforms: ["sample"]
},
},
});
sd.buildAllPlatforms();
transformer周りについては、
- registerTransformでtransformを定義する
- matcherがbuild対象JSONに入っているtokenの数だけ、各tokenを引数にもって実行される。matcherの返り値がtrueになるように返り値を設定してやると、そのtokenがtransformerに引き継がれる
- 渡されたtokenのvalueを直接置き換える。これがそのままCSS propertyになる
- 3のキー名にしたい文字列をtransformerの返り値として返す。
でいけるかと!
この辺の仕様は近日中にまとめようと思います!
株式会社エアークローゼットでは、デザインやエンジニアリングにとどまらず、様々な領域を横断するプロジェクトに携わることができます。
興味がわいた方、ぜひ採用ページを覗いてみてください↓↓
Discussion