見出し画像

Figmaからさまざまなカラートークンを生成して最高の色世界を保守する noteUIDev#1

この記事は、Figma 開発 Advent Calendar 2022 の17日目の記事です。

こんにちは 🐈

ぼくは CDO 室直下のデザインシステムプロジェクトで、コンポーネントライブラリ開発をしている UX エンジニアです。デザインと実装を繋いで、一貫性のある体験を提供することをテーマ取り組んでいます。これから少しづつ日々のチームでの開発 note を書いていきますので、お時間あればお付き合いください◎

_ _

これはデザイナーが把握している色世界を、実装側にいろいろなスタイリング環境に応じた形式で Figma から配るはなしです。

概要を手短にぎゅっとしてみると

  • Tokens Studio で Figma からカラーパレットを JSON 化して GitHub に PR を作成

  • token-transformer と style-dictionary を使ってもろもろを加工、CSS Variables, Sass Variables, JSON for tailwind.config, JSON for Styled Components などを生成

  • 上記アセットのビルドと prerelease などを Github Actions で自動化しつつ Github Packages から配信

というような流れになっています。

_ _

では、背景と詳細を連ねていきます。

スタイリング管理の難しさに向き合う

現在 note では、CSS・Sass・Tailwind CSS・Styled Components (Next・Nuxt・Svelte・WebComponents)など、さまざまなスタイリング環境があります。これらはもともとそれぞれで管理していたのですが、トークンとして抽出できるものは、共通パッケージから参照したいということで、yml に一部の色を記述してそれを変換し、CSS VariablesCSS Custom Properties)を生成していました。

light:
  noteGreen: "#238F76"
  likeRed: "#ea3f60"

dark:
  noteGreen: "#25C1A9"
  likeRed: "#FF6D91"

👆🏻 こういう yml を元データにして、

:root {
  --color-noteGreen: #238F76;
  --color-likeRed: #ea3f60;
}

@media only screen and (prefers-color-scheme: dark) {
  html.ios-webview,
  html.android-webview {
    --color-noteGreen: #25C1A9;
    --color-likeRed: #FF6D91;
  }
}

👆🏻 ダークモードに対応したスタイルに出力しています。

そこから時は流れていくにつれ、それ以前の負債で目をつぶっていたこともありますが、Sass 関数などによる自由な色定義などが目立つようになり、デザインと実装、双方の色管理が把握しにくいカオスな状態を、なんとか解消したいと考え始めました。

たとえば、グレーと黒の色・透明度違いが20種類以上あるとか、変数が参照されているかどうかわからないとか、具体・抽象の命名が曖昧になっているとか、…などなど、高速でフットワークの軽い自律的な開発の負債として、こういう状況が生まれていました。

いろんなグレーの色定義が調査で発覚した
「白って200色あんねん」ってポジティブワードだったような?

そのことから実装面では、より制約を持たせやすい CSS Variables と Tailwind CSS を主軸にしようと考えました。ただし後方互換性といいますか、完全にアプリケーションのすべての項目を置き換えていくのは難しいため、既存の形式も維持するようにします。

Tokens Studio

デザインと実装の乖離を無くすためには、元となるカラートークンを Figma から管理・生成できるスタイルがいいなと思い、Tokens Studio(ex. Figma Tokens)を採用しました。

登録したスタイルデータをまとめた JSON を生成し、GitHub に PR を作ってくれるので、これでデザインと実装の接点をスムーズに繋げることができます。

One source of truth for design and development.
Manage your brand at scale and bring efficient processes to your design and development teams - all while staying in full control over your data.

https://tokens.studio/

また、note app の webview では、ダークモードを採用しているのですが、Tokens Studio はテーマをいい感じにケアしてくれるところも推しポイントです。テーマ機能(Token Sets)を利用することで定義した色にエイリアスを貼ったり、global tokens と alias tokens の棲み分けをしています。

Tokens Studio 導入の前に下拵えとして、グレーを数種類に丸め、透過度にルールを作り、Sass 変数を CSS variables に置き換える作業を、気合いで進めていきます。気合いで進めていきます🔥

色の棚卸しと整理整頓に目処がついたら、Tokens Studio と連携したい Github レポジトリを接続します。こちらの記事が丁寧に説明されていて参考になるかと思いますので、省略します。PAT を用意する一手間が必要になっています。

Tokens Studio と Github は import / export 相互に同期できつつ、Figma の Color styles も import できるところがポイントで、既存の資産をしゅっと流用できるので、導入のコストがとても軽いです。

Tokens Studio の入力画面
GUI 入力と、JSON直接編集に対応してる

また実際の note の実装では、色情報だけでなく、タイポグラフィをはじめ要素サイズなどもここに定義して配信しています。グローバルなスタイルに関わる変数にして取り扱いたいものは、すべてここに相乗りさせるようにしています。

生成したJSONを成形

生成してきた JSON を実装側でいろいろと成形していきます。Tokens Studio が作ってくれる JSON は次のように、

{
  "global": {
    ...
  },
  "light": {
    "color-text-primary": {
      "value": "{color-gray-900}",
      "type": "color"
    },
    "color-text-secondary": {
      "value": "{color-grayAlpha-600}",
      "type": "color"
    }
  },
  "dark": {
    "color-text-primary": {
      "value": "{color-whiteAlpha-800}",
      "type": "color"
    },
    "color-text-secondary": {
      "value": "{color-whiteAlpha-600}",
      "type": "color"
    }
  }
}

エイリアスやテーマ情報が混じった状態になっているので、まず Token Transformer でそれらを解決します。

npx token-transformer ./tokens.json ./light.json global,light global

👆🏻 このようにコマンドを実行すると、

{
  "color-text-primary": {
    "value": "#08131A",
    "type": "color"
  },
  "color-text-secondary": {
    "value": "#08131AA8",
    "type": "color"
  }
}

👆🏻 このようにエイリアスを解決しつつ、テーマを切り出した light.json を生成してくれます。これをもとに、今度は StyleDictionary を使って、各環境のスタイルを生成していきます。

ちょっと API の仕様を読み込む必要がありますが、format オプションで環境を指定して、source で利用するデータを指定します。transforms では環境に応じて値の単位を変えるなど、必要な関数を書いて加工をしています(カラーには必要ないですが)。

const StyleDictionaryPackage = require('style-dictionary')

function getStyleDictionaryConfig({source, transforms, files}) {
  return {
    source,
    platforms: {
      css: {
        transforms,
        files,
      },
    },
  }
}

const resources = [
  {
    source: [
      './global.json',
    ],
    transforms: ['number2px', 'flattenBoxShadow'],
    files: [
      {
        'destination': './variables.css',
        'format': 'css/variables',
        'options': {
          "outputReferences": true
        }
      },
      {
        'destination': './variables.scss',
        'format': 'scss/variables',
        'options': {
          'outputReferences': true
        }
      },
    ],
  },
  {
    source: [
      './global.json',
    ],
    transforms: ['flattenBoxShadow'],
    files: [
      {
        'destination': './variables.json',
        'format': 'json/flat',
      },
    ],
  },
]

resources.map((resource) => {
  const StyleDictionary = StyleDictionaryPackage.extend(getStyleDictionaryConfig(resource))
  StyleDictionary.buildAllPlatforms()
})

👆🏻 global テーマから作った global.json を CSS variables・SCSS variables・Styles Components 用の JSON に成形しています。

/* CSS */
:root {
  --color-whiteAlpha-900: #FFFFFF;
  --color-whiteAlpha-800: #FFFFFFE6;
  --color-whiteAlpha-700: #FFFFFFD1;
  --color-whiteAlpha-600: #FFFFFFA8;
  --color-whiteAlpha-500: #FFFFFF80;
  --color-whiteAlpha-400: #FFFFFF66;
  --color-whiteAlpha-300: #FFFFFF52;
  --color-whiteAlpha-200: #FFFFFF38;
  --color-whiteAlpha-100: #FFFFFF1f;
  --color-whiteAlpha-50: #FFFFFF08;
  // ...
}
// Sass
$color-whiteAlpha-900: #FFFFFF;
$color-whiteAlpha-800: #FFFFFFE6;
$color-whiteAlpha-700: #FFFFFFD1;
$color-whiteAlpha-600: #FFFFFFA8;
$color-whiteAlpha-500: #FFFFFF80;
$color-whiteAlpha-400: #FFFFFF66;
$color-whiteAlpha-300: #FFFFFF52;
$color-whiteAlpha-200: #FFFFFF38;
$color-whiteAlpha-100: #FFFFFF1f;
$color-whiteAlpha-50: #FFFFFF08;

👆🏻 出力はこんな感じ。

StyleDictionary はこのように、Token データを任意のスタイル形式に変換してくれるパッケージになっています。あらゆる環境に対応できると謳っているだけあって、アプリのスタイリング用データも生成できます。このあたりもカラーパレットを共有するなら将来的に対応したいと思っています。

Tailwind の設定ファイルは、よしなに必要なトークンをフィルタリングして theme オブジェクトに与えます。

// tailwind.config.js

function getColorTokens(tokens) {
  return Object.keys(tokens).reduce((prev, current) => {
    const tokenValue = tokens[current]
    const key = current.replace('color-', '')
    const lowerCamelCaseKey = key[0].toLowerCase() + key.slice(1)

    prev[lowerCamelCaseKey] = `var(--${current}, ${tokenValue.value})`
    return prev
  }, {})
}

const lightTokens = require('./light.json')
const lightColors = getColorTokens(lightTokens)
const globalTokens = require('./global.json')
const globalColors = getColorTokens(globalTokens)

const colors = {
  ...lightColors,
  ...globalColors,
  transparent: 'transparent',
  current: 'currentColor',
  inherit: 'inherit',
}

/**
 * @type {import('@types/tailwindcss/tailwind-config').TailwindConfig}
 */
module.exports = {
  content: ['./src/**/*.{vue,js,ts,jsx,tsx}'],
  theme: {
    colors,
  },
}

👆🏻 実際は color 以外の要素も同じように、必要に応じて加工して設定に食わせています。

GitHub Actions(CI)で自動化

Tokens Studio が JSON をコミットしてれるのにフックして、その JSON を成形するスクリプトを package.json に登録しておいて CI で動かします。ついでに prerelease を作って Github Pakages で公開するところまで自動化しています。

name: tokens-to-styles

on:
  push:
    branches:
      - figma
    paths:
      - ./tokens.json

env:
  NODE_VERSION: 16.12.0

jobs:
  convert_tokens:
    name: convert figma tokens
    runs-on: ubuntu-latest
    steps:
      - name: 🔔 checkout
        uses: actions/checkout@v3
        with:
          ref: ${{ github.head_ref }}

      - name: 🌱 setup node
        uses: actions/setup-node@v3
        with:
          node-version: ${{ env.NODE_VERSION }}
          registry-url: https://npm.pkg.github.com/

      - name: 📦 install packages
        run: npm install

      - name: 🔨 build assets
        run: npm run build

      - name: 🐈‍⬛ git commit
        uses: stefanzweifel/git-auto-commit-action@v4
        with:
          commit_message: 🤖 CI build

      - name: 👤 set author
        run: |
          git config --global user.email "push@no-reply.github.com"
          git config --global user.name "GitHub Push Action"

      - name: 🔼 automated version bump
        uses: phips28/gh-action-bump-version@master
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          default: prerelease
          preid: canary-tokens
          target-branch: figma
          commit-message: 🤖 CI bumps version to {{version}}
          rc-wording: Figma,figma

      - name: 🚀 publish
        run: npm publish --tag canary-tokens
        env:
          NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}

👆🏻 figma ブランチで tokens.json に変更があったら CI が走るように運用しています。

成果に感動◎

以上のことから、Figma の Tokens Studio でカラーを管理してやると、PR と prerelease のパッケージを配布するところまでを自動化することに成功しました。以前までは CSS の知識のあるデザイナーがコミットしていたところや、エンジニアとコミュニケーションしていたところを意識したり管理しなくてよくなりました。

また、この実装でカラーまわりの整理整頓をしていくなかで、カラーパレットシステムを導入する機運が高まり(global token / alias token / like matelial Material Design)、いざ導入となると、かなりスムーズに色を試したり変更に堅牢な状態を確認することができました。

カラーをさらに整理していく作業には骨が折れましたが、別な環境へのカラー生成部分が自動化されていることと、Figma 上でデザインと相違ないことを確認しながら作業できる安心感があって、大手術にも臆することなく取り組めた実感があります。

元々この作業を進める前は、Figma には頻出して使う数種類の色、実装側には無数の数えきれない定義、という状態だったのが、今ではほぼ Figma で定義した色と実装側で定義している色が一致しています。感動。。。。。。

色世界を守るために

この最高の環境を保守していくために、新しい色定義が無秩序に増えていかないよう、 Stylelint でチェックして使える色を制限しています。

{
  "plugins": [
    "stylelint-declaration-strict-value"
  ],
  "rules": {
    "declaration-property-value-disallowed-list": [
      {
        "/(color|border|background)$/": ["/^.color/", "/^var.--color-[a-zA-Z]+.$/"]
      }
    ],
    "scale-unlimited/declaration-strict-value": ["/color$/", {
      "ignoreValues": ["transparent", "currentColor", "inherit", "initial", "-webkit-focus-ring-color"],
      "ignoreFunctions": false
    }]
  }
}

👆🏻 主要な色指定に --color-**  な CSS variables しか使えないようにしています。さらに Sass 関数を整理して制限したり、プロパティと色のマッチングをもう少し細かく見て、詳細にチェックできるようにカイゼンしようと考えています。今は過渡期なのでゆるめ。

周知やドキュメントでプロダクト開発者に伝えるには限界があるので、このように自動的に気付いたり学習できる仕組みがあるといいですよね。合わせて置き換えガイドや関連 PR をまとめて見返せるようにもしています。

ちなみに、色の置き換え作業をだいたい済ませた気持ちでこの linter を有効にしたところ、393件のエラーが出て、ビビり倒しました🔥

GitHub上のコメント上で、大量のエラーを閉じて隠している
邪魔というより怖いので閉じた

_ _

いろいろおすすめ資料

前提知識や飛ばしてしまった文脈が多々あるので、あわせて読みたい、な参考文献・資料をまとめてみました。

Ubie Design Tokensを公開しました

Ubie さんは API を叩いてトークンを生成して Style Dictionary に渡しているみたいですね。note でも Icon 生成や画像アップロードで直接 Figma API を利用していますが、扱いやすくてすきです。

Figma Tokens で小さくはじめるデザインシステム

Tokens Studio(ex. Figma Tokens)の便利さをざっくり知るのにこちらを見るとよいかなと。Tokens Studio は激ヤバにいけてるプラグインで、いろんな活用方法があって楽しいです。ドキュメントが充実しているので、そちらを参照するといいかな思います。

Figma Tokens から名前を変えたのもあって、トークンを可搬性のあるものとして捉えて、別のデザインツールや時代の流れにも答えてくれそうでいいですね。

Design tokens

そもそも design tokens(トークン)の構造や表現を理解するにはこちら。ざっくりいうと、global tokens は文脈のないプリミティブな値、alias tokens は文脈を持ち複数の場所から参照される値、component-specific tokens は特定のコンポーネントに依存したローカル値です。

日本語で読みたい + 掘り下げて理解したいときにはこちらの記事が理解度が上がってよいかなと思います。

Tailwind考

デザインシステムを始めたい → スタイリングのルール化を考えるとき、 Tailwind CSS はちょうどいい選択肢かと思います。前述した Linter を入れずとも Tailwind の class がなければ色指定できないので、保守性が高く、ほどよい浸透圧でおすすめです。

Tailwind CSS の一歩進んだ書き方

Tailwind いいぞの補強として、気が利いてるポイントを知っておくと、しゅっと書けるようになりますし、なんでもできるな〜となります。なんでもできるとなると用法容量を守らないとカオスが生まれやすいということでもあるので、プロダクトでやりたいことの需要と提供しているトークンがマッチしているかどうか見直すことも重要だと思います。

_ _

 おわりに

今回は Figma からカラー生成の自動化について書きました。noteのようなひとつの大きなプロダクトにおいて、堅牢で柔軟なシステムができたかなと実感していますが、例えば、複数の小さなプロダクトを少数のデザイナーで運用しているようなシチュエーションにもいいでしょう。コンポーネントライブラリを導入するには現実的ではなくても、それぞれのサービスのトークンをテーマとして用意して、同じ生成システムで自動配布するだけでも、デザインや実装の生産性を向上してくれると思います。

また、受託制作で毎回案件が変わるような現場でも、このシステムを利用すると、案件ごとにテーマを入れ替えるだけで、同名の変数で運用できるので、ボイラープレート的なツールの使い勝手をワンランク上げてくれるはずです。

この開発の道中では、カラーパレットの導入やその先のビジョンへの対応などにも取り組んでいるので、デザイン面で話せることも多いかと思います。そんな記事も(チームから)発信できたらと🐈


おわります。

この記事は、Figma 開発 Advent Calendar 2022 の17日目の記事でした。