この記事の概要
システマチックなプロダクト開発をするにあたり、Design tokens の存在は当たり前になってきました。
その際、以下の図にあるように、いくつかの階層を作って管理するパターンが多いです。
こういった考え方を Tailwind CSS で実現しようと思った際に少し苦労したので、備忘録として記事に残しておきます。
なおこの記事では分かりやすくするため colors だけに言及していますが、fontSize や spacing などでも同様です。
検証した環境
次のリポジトリで検証していました。
Tailwind CSS のバージョンは 3.3.3 です。
全体の構成は次の通りです。
.
├── dist
│ └── output.css
├── index.html
├── input.css
├── node_modules
├── package.json
├── pnpm-lock.yaml
└── tailwind.config.js
開発時は次のコマンドを叩いています。
Tailwind CSS の Installation に記載してあるのとほぼ同じで、変わったことは何もしていません。
npx tailwindcss -i ./input.css -o ./dist/output.css --watch
実現したい状態
- Global tokens を指定する
- デフォルトの colors
- 上記に加えブランドカラーのパレット
- Alias tokens を指定する
-
surface = gray.100
,primary = brand.500
など
-
- 基本的には alias tokens を使うものの、 global tokens も使えるままにしておく
どこかで定義した色に対してエイリアスを貼れる方法はないものかと探しました。
結論: 一旦の完成系
次のように、module.exports
の外で brandColors
を定義することで、bg-gray-100
のような指定も bg-surface
のような指定も、どちらもできました。
/** @type {import('tailwindcss').Config} */
const brandColors = {
50: "#e3f7df",
100: "#bfeeb4",
200: "#a5e987",
// ...
950: "#0a3900",
};
module.exports = {
// ...
theme: {
extend: {
colors: ({ theme }) => ({
brand: {
...brandColors,
},
surface: theme.colors.gray[100],
"on-surface": theme.colors.gray[950],
primary: brandColors[500],
"on-primary": brandColors[50],
"primary-container": brandColors[100],
"on-primary-container": brandColors[600],
}),
},
},
// ...
};
背景: 通常の Tailwind の指定の仕方
最初にドキュメントなどを眺めていたときは 実現したい状態
のためには、tailwind.config.js
がこんな雰囲気になりそう?と感じていました。
module.exports = {
// ...
colors: {
brand: {
50: "#e3f7df",
100: "#bfeeb4",
// ...
500: "#55c500",
// ...
950: "#0a3900",
},
primary: "#55c500", // brand[500] とまったく同じ hex 値なのでイマイチ
"on-primary": "#e3f7df", // brand[50] とまったく同じ hex 値なのでイマイチ
// ...
},
// ...
};
実際、Tailwind にはデフォルトでは良い感じにエイリアスを貼れる方法がありません1。
Alias tokens を使わない or 上記のようにハードコーディングしてもそこまで大きな問題は無いような気がしつつ、好奇心で色々調べてみた次第です。
試したけどダメだった内容
theme 直下で指定する
module.exports = {
// ...
theme: {
colors: {
brand: {
50: "#e3f7df",
100: "#bfeeb4",
200: "#a5e987",
// ...
950: "#0a3900",
},
},
extend: {},
},
// ...
};
この指定だと、alias tokens がどうとか以前に Tailwind のデフォルトの色がすべてなくなってしまいます。
extend
を使わずに theme
に直接追加するのは 元々あるパレットに追加
ではなく 1 からすべてを自分で作りたいとき
なので、論外です。
公式ドキュメントのうち、次の部分にも記載がありました。
extend 内で新たな色を指定して theme()
を使う
module.exports = {
// ...
theme: {
extend: ({ theme }) => ({
brand: {
50: "#e3f7df",
100: "#bfeeb4",
200: "#a5e987",
// ...
950: "#0a3900",
primary: theme("colors.brand[500]"),
"on-primary": theme("colors.brand[50]"),
},
}),
},
// ...
};
公式ドキュメントのうち、次の部分を読んで勘違いしていました。
If you need to reference another value in your theme, you can do so by providing a closure instead of a static value. The closure will receive an object that includes a theme() function that you can use to look up other values in your theme using dot notation.
ですが、よく読めば another value in your theme
と書いてあります。
colors
の中で colors
を参照することはできませんでした。
その証拠に(?)コンソールに出るエラーも RangeError: Maximum call stack size exceeded
でした。
上手くいったきっかけ
次のドキュメントを読みました。
この内容をそのまま解釈すると「自分でプリセットを用意できる」ですが、それはそれで既存の設定が失われてしまいます。
ただ、このように module.exports
外のオブジェクトを引っ張ってくるのもありなら、上手く粒度を分ければ解決するのでは?と思ったのです。
冒頭に記載した通り module.exports
外に brandColors
のオブジェクトを用意して、extend
内で展開し、alias の指定も brandColors
をもとにすることで解決できました。
最後に
Global tokens と Alias tokens (規模やチームによっては Component specific tokens も)の階層を分けてスタイリングをするのはもう当たり前かと思っていましたが、これだけ流行っている Tailwind CSS でデフォルトでサポートされていないのは意外でした。
認知負荷を下げる意味でも Alias tokens までは設定する方が良いと思っています。
似たような考えの人の助けになれば幸いです。
最後まで読んでくださってありがとうございます!
X (Twitter)でも情報を発信しているので、良かったらフォローお願いします!
Devトークでお話してくださる方も募集中です!
-
私が見つけられていないだけ、とも疑っているので、もしあれば教えていただきたいです! ↩