Tailwind CSSでデザインシステムを構築する[後編]
~デザイントークンを定義するときに何を議論すべきか

2024年1月26日発売の『Tailwind CSS実践入門』の第9章「ユーティリティファーストでデザインシステムを構築する」の一部を、前後編の2回に分けて掲載します。ユーティリティファーストというTailwind CSSの発想を活かしたデザインシステム構築の最初の一歩をまとめたものです。開発者がデザインルールをTailwind CSSの設定に落とし込む過程はもちろん、デザイナーが開発者とどのように協力しあうべきかについても論じています。
本記事の内容は、書籍の最終章に当たる部分です。ほかの章の内容に言及していたり、書籍内ですでに使われた用語が説明なく登場したりするのを防ぐため、書籍の原文から一部を変更しています。


前回の記事の最後で、デザインシステムを構築する際にコンポーネント集を最初のマイルストーンとしないほうがよい(ボトムアップで始めるほうがよい)という主張をしました。デザインシステムをコンポーネント集とみなしていると、かなりの数が出そろうまではファーストリリースが不可能になるからです。⁠まずコンポーネントがあって、それでは賄えないものを例外的な方法で扱う」のではなく、⁠すべてのUIは(たとえあるページにしか出現しない特殊なものであっても)同じルールで記述できる」状態を目指すべきです。

ではコンポーネントをあと回しにすると仮定したとき、デザイナーが最初に手を付けるべき対象は何でしょうか。前回の記事では色やスペーシングのルールといった、例示に基づく説明だけを行ってきました。以下では、それらを「デザイントークン」design tokensという用語で総称しながら説明していきます。

デザイントークンとは何か

デザイントークンとは、デザインシステムという言語における最小の符号です。たとえば、そのデザインシステムの中で使ってよいとされる色(その名前とカラーコード⁠⁠、フォントの種類、スペーシングの値などがデザイントークンにあたります。UIデザインを文章を書く行為にたとえたとき、デザイントークンはその最小単位、単語や文字に相当するものと考えられます。その名のとおり、UIデザインにおける字句tokenです。

デザイントークン⁠ユーティリティ⁠コンポーネントはそれぞれ別物

ここではCSSの文脈のみを扱っていますが、本質的にはデザイントークンはCSS(ないしWeb技術)から独立した概念です。たとえばあるサービスがWeb、iOS、Android向けに別々のクライアントアプリケーションを提供していたとして、トークンがそれらで同じという状況があり得ます。実際には各OSの標準などを考慮するので完全に共通とまではいかないでしょうが、少なくともデザイントークンはプラットフォームから独立した概念ととらえたほうが適切です。それらがたまたま、CSSという形で表現されたときにユーティリティという形をとると言えます。

ユーティリティの存在を前提にデザインシステムの構成要素を整理すると、大きく3つの層に分けられます図1⁠。プラットフォームから独立した値の定義である「デザイントークン⁠⁠、各プラットフォームでの基本的な記述方法(たとえばクラス)を提供する「ユーティリティ⁠⁠、そして繰り返し使うUIを抽象化した「コンポーネント」の3つです。

図1 三層構造
図1

具体例をあげると、色の名前そのものはデザイントークンとしてデザイナーが定義します(これはプラットフォームからは独立しています⁠⁠。次に、その色に対応するbackground-colorプロパティやcolorプロパティを表現するクラスをユーティリティとして定義します。ユーティリティの段階でプラットフォームから独立ではなくなります。なぜなら背景色や文字色をどういう言葉で表現するかは、たとえばWebとiOSアプリでは異なるからです。

最後に、それらのクラスを用いて繰り返し現れるイディオムのようなUI(たとえばボタン)をコンポーネントとして表現します。コンポーネントはスタイル(CSS)だけではなく挙動についての定義も含みますから、この層をライブラリとして表現するにはほかの(たとえばTailwind CSS以外の)技術も必要になってきます。

ここまでの主張をまとめると、図1の一番下にある値(デザイントークン)をデザイナー主導で定義しつつ、真ん中の層をTailwind CSSで表現するのが最初のマイルストーンとして良いということです。ページ内のすべての要素が共通コンポーネントでできているとは限らないことを前提に、しかしすべての要素をトークンから作られたユーティリティを用いて表現できる状態に持っていくことが、浸透しやすいデザインシステムを作るコツというわけです。

デザイントークンの管理に適したツール

Amazonが開発しているStyle Dictionaryというツールがあります。これはデザイントークンを定義したJSONファイルからWeb向け(CSSやTypeScript⁠⁠、Android向け(XMLやKotlin⁠⁠、iOS向け(Swift)……のコードを生成します。Tailwind CSSとは直接的には無関係なツールですが、これはデザインシステムにおけるトークンの概念をよく表しています。本質的にはJSONやYAMLで書けるほどの静的な値でよいのです。したがってデザイントークンを作る過程とは、デザイナーがFigmaなどのツール上にルールを定義し、それらをいったんCSSとは独立した形でソースコードへ同期するしくみづくりにほかなりません。

デザイントークンの管理方法はなんでもかまいませんが、おおむね以下の要件を満たすツールがよいでしょう。

デザイナーと開発者が両方参照できる
策定の権限を持つのは基本的にデザイナーだが、片方しか見られないツールは避ける。ローカルにインストールが必要だったり、特定の権限を持った人しか参照できない情報は他人事になるため
デザインのワークフローに組み込める
デザインシステムは、デザイナーが日常のUIデザインで活用しているはず。したがって、デザイン時に使える選択肢が参照できる形で管理されているほうがよい
開発エコシステムと連携がしやすい
可能ならWeb APIを通じてデザイントークンの情報が取得できるツールがよい。変更があったときに自動で反映することで、実装とドキュメントの乖離を防ぎやすくなる

ドキュメント用のツールや、オフラインのデザインツールなどでもおそらく管理は可能でしょう。しかしすべての条件を満たせるのは、ドキュメント機能を持ったオンラインのデザインコラボレーションツール(Figma、Sketch、InVision、Zeplin)です。特に現代ではFigmaを選択するケースが多いでしょう。

FigmaにはStylesという形で色などを複数のファイルで共有する機能があります。また、Figmaのファイルそのものをドキュメントとして作成することも容易ですし、Figma Communityというプラットフォームにそれを公開できます。公式APIもありますし、プラグインなどの強力なエコシステムによって開発ツールとの連携も可能です。以下、特に理由がない限りデザインツールの例としてはFigmaを用います。

トークンの命名─⁠─値どおりの名前(リテラル)か⁠意図による名前(セマンティック)

色やスペーシングを命名する際の方針には大きく分けて2つあります。たとえば大きさが8pxのスペーシングに対して、そのまま8と名付けるか、あるいはsmallsのように名付けるかです。前者を「リテラル」な命名、後者を「セマンティック」な命名と呼びます。

これらは筆者の用語であり、Tailwind CSS公式で(あるいはデザインシステムの分野で)決まった用語があるわけではありません。人によってはリテラルと言わず「プリミティブ」という場合もありますし[1]、サイズ限定ですがsxsのような命名を俗に「Tシャツ風サイジング」⁠T-shirt sizing)と呼ぶこともあります[2]

リテラルかセマンティックかという対比は絶対的でなく、相対的なものです。つまり「よりリテラルな命名」⁠よりセマンティックな命名」のように比較級で表現するのがふさわしいということです。ただし、どのくらい細かい段階に分かれるかは分野(色・スペーシング・文字サイズなど)によって変わります。たとえば色に対してblue-300と付けることは、カラーコードをそのまま使うことに比べると抽象化されていますが、primaryと付けることに比べるとリテラル寄りです。トークンを作る際は、どのくらいの抽象度で命名するのが自分たちにとって最も理解しやすいかを調整する過程が必要です。

Tailwind CSSは、ユーティリティクラスこそCSSのプロパティ名そのままといった命名ですが、スペーシングなどセマンティックな命名を採用している分野もあります。たとえばp-4は4pxにするクラスではなく、16pxです。スペーシングは4pxを一単位とし、それが何個分あるかを名前にしているという点で、実はあまりリテラルな名前ではありません。もしあなたのチームが、416pxであるのは覚えにくい、padding: 16pxp-16であるほうが扱いやすいという判断をするのなら、よりリテラルな命名に寄せてしまうのもよいでしょう。

カラーパレット─⁠─トークンに階層を設けるべきか

先の項で紹介した「リテラル」「セマンティック」かという判断軸は、色の命名を決める際にとりわけ重要です。なぜなら、どちらを選ぶかによってダークモードをはじめとする複数テーマへの対応方法がまったく変わるからです。Tailwind CSSのデフォルトテーマは、色名についてはややリテラルな方針を採っていると考えられます。

Tailwind CSSを離れて一般的に考えた場合、リテラルな色名とセマンティックな色名はどちらか一つを選ぶというより、二段構えで設計するという方針もあり得ます図2⁠。まず基礎的なカラーパレットとして、リテラルな色名にカラーコードを結び付けるような関係を定義しつつ、そのうえでセマンティックな色名にどの色名を割り当てるかを定義するといった形です。たとえば、blue-400#60a5faであると定義したうえで(リテラル⁠⁠、このサイトのprimaryblue-400であるといった定義をします(セマンティック⁠⁠。

図2 二階建てのトークン
図2

カラーテーマにおいてトークンを二階層に分ける場合、ダークモード対応は階層間の結び付き方にバリエーションを加える形で表現されます。つまりprimaryという色はライトテーマではblue-400だが、ダークテーマではblue-500である」といった形で表現されます。実装としては色名をCSS Variablesで表現する形になるでしょう[3]

module.exports = {
  theme: {
    colors: {
      primary: "var(--semantic-color-primary)",
      // ...
    },
  },
};
:root {
  --semantic-color-primary: var(--literal-color-blue-400);
}

@media (prefers-color-scheme: dark) {
  :root {
    --semantic-color-primary: var(--literal-color-blue-500);
  }
}
<div class="bg-primary">
  コンポーネントごとにライト・ダークの背景色を分岐している例
</div>

一方、Tailwind CSSのデフォルトテーマがそうであるように、リテラルな色名の一階層のみでカラーテーマを表現する場合、ダークテーマの対応はマークアップ上の分岐として表現されます。つまり、⁠このコンポーネントのここの背景色はライトテーマではblue-400だが、ダークテーマではblue-500である」といった形になります。実装としてはTailwind CSSのdark:モディファイアを使う形になるでしょう(Tailwind CSSでは、クラスの前に付けるモディファイアによって特定の条件での見た目を表現します⁠⁠。

<div class="bg-blue-400 dark:bg-blue-500">
  コンポーネントごとにライト・ダークの背景色を分岐している例
</div>

どちらが望ましいかはあなたのチームで判断するほかありません。どちらにも一長一短があります。Tailwind CSSが二階層を採用していない理由として、作者のAdamは1つの色がライトとダークで常に同じ変化をするとは限らないからというのをあげています。現実には、どの色がどの色に変化するべきかは状況依存であり、結局セマンティックな色名を大量に用意しなければならないことになるだろうというわけです。

図3GitHubをブラウザの開発者ツールで開いたときのスクリーンショットです。ダークモードのときにどこがどの色になるか、notification(通知)workflow(ワークフロー)といった機能別に変化のしかたが定義されているのがわかります。

図3 GitHubで大変なことになっているCSS Variables
図3

この問題を避けようと思った場合(二階層のトークンでうまく行かせる場合⁠⁠、セマンティックな色名は非常に注意深く行わなければならないというのは真実でしょう。筆者の関わったデザインシステムでは、セマンティックな色名にはできるだけ利用文脈を含まないことでこの問題に対処しました。つまり、デザイナーはmodal-background-colorのような命名はけっして行わず、background1などの無内容な色名のセットを用意してそれらを使い分けるという方針を採っています。ひとたび〇〇-background-colorのような名前が生まれると、それは際限なく増えていってしまうからです。色名に用途を含めたくなる誘惑に、デザイナーたちは抗う必要があります。おそらくこの場合、セマンティックな色名の総量は非常に少ないことになるでしょう(それが許されるサイトであるかを判断軸に入れるとよいでしょう⁠⁠。

また、二階層のカラーテーマを運用しながらダークテーマをサポートしたい場合、例外的な色を使ったコンポーネントはダークテーマに対応できないという制約を受け入れる必要があります。この前提におけるダークテーマ対応とは、たとえばbackground1「ライトテーマで白く、ダークテーマで黒い」という挙動をすることに依存しているからです。⁠このコンポーネントは例外的にgray-300を直接使う」と指定したときには結局dark:モディファイアを使うことになるか、さもなければそのコンポーネントはダークモード非対応にするしかありません。おそらくこれこそTailwind CSSがdark:モディファイアを提供している理由であり、二階層のカラーテーマを使うと決めたチームが例外的な色をできるだけ避けなければならない理由でもあります。この制約にコミットするかは最初の段階で決めておくのがよいでしょう。

ここまで、トークンを二階層に分ける運用を紹介し、その方式のメリットや、Tailwind CSSがデフォルトではそれを採用していない理由を述べてきました。人によっては、コンポーネントのどの部分にどのセマンティックトークンを割り当てるかという部分を三階層目のデザイントークンと位置付ける例もあります図4⁠。二階層目のセマンティックな色名には用途を含めない代わり、たとえばmodal.root.backgroundという「位置名」background1を割り当てるといった関係を定義するのです。しかしこれはユーティリティというよりもコンポーネントの設計までデザイナーと開発者間で握ることを目指す話であり、Tailwind CSSが扱える範囲を超えてしまうため、詳しくは扱いません。

図4 三階建てのトークン
図4

本記事では命名における指針のみを扱ってきましたが、実際にどういう色をカラーパレットに加えるべきかについてはデザインの専門書を参照するのがよいでしょう。たとえばTailwind CSS作者のAdam WathanとデザイナーのSteve SchogerはUIデザインに関する『Refactoring UI』という電子書籍を発行しています。この中にある「Working with Color」という章では、HSL色空間ベースでの色の設計やプライマリーカラーの決め方といったトピックを扱っています。Tailwind CSSのデフォルトのカラーパレットがどのように設計されたのか、その背景を補完する資料としても読むことができます。

『Tailwind CSS実践入門』第9章では、色以外にもさまざまなデザイントークンを扱っています。スペーシングや文字の大きさについても、ありうる論点と選択肢をできるだけ提示するという姿勢で書かれているので、自身の課題感やプロダクトの事情などを頭において読むのがよいでしょう。

おすすめ記事

記事・ニュース一覧