uhyo/blog

Tailwind考

2022年9月30日 公開

皆さんこんにちは。最近とある事情でTailwind CSSにわりと真剣に向き合わないといけなくなった筆者です。

Tailwind CSSの話題は、Twitterのフロントエンド界隈では定番のトークテーマのひとつです。しかし、筆者の考えを文章にまとめたことは無かったので、このたびブログ記事にすることにしました。

結論

筆者が一番みなさんに伝えたいことは、Tailwind CSSは考え無しに採用してよい技術ではなく、採用するには熟慮が必要だということです。とくに、フロントエンドのスターターキット的なプロジェクトの中にTailwind CSSが混ざっていることがありますが、あれはけっこうな罠です。気軽に採用すべきものではありません。

筆者の考えでは、Tailwind CSSの採用を考慮に入れてよいのは次の2つの場合です。

デザインにこだわりがなく、最低限整っていればいい場合。デザイナー不在のプロジェクトなど、デザイン上の細かな要求が無いがとりあえず整えておきたいという場合は採用してもよいかもしれません。Bootstrap的な立ち位置のものが欲しい場合です。

逆に、デザインシステムをしっかりと整備しそれからの逸脱を防ぎたい場合。ただデザインがあるだけでなく、フロントエンドエンジニアまで巻き込んでデザインシステムを整備したいということが重要です。このようなユースケースではTailwind CSSのアプローチが適している可能性があります。

この記事では、上記の結論に至るまでの考えの道筋を紹介します。

Tailwind CSSとは

まず、Tailwind CSSがどのような技術なのかをおさらいしましょう。一言で言えば、Tailwind CSSはメタCSSフレームワーク、あるいはCSSフレークワークジェネレータです。公式サイトではこのことが次のように表現されています。

Tailwind includes an expertly crafted set of defaults out-of-the-box, but literally everything can be customized — from the color palette to the spacing scale to the box shadows to the mouse cursor.

Use the tailwind.config.js file to craft your own design system, then let Tailwind transform it into your own custom CSS framework.

(筆者による訳)Tailwindには、うまく作られたデフォルトの設定が最初から付属しています。しかしあらゆるものがカスタマイズ可能です。カラーパレット、スペースの大きさ、ボックスシャドウからマウスカーソルに至るまで、全てです。

tailwind.config.js を用いてあなたのデザインシステムを組み立てましょう。Tailwindはそこからあなた用にカスタムされたCSSフレームワークを生み出します。

2段落目に書かれているように、Tailwindのコアとなる機能は「CSSフレームワークを生成する」ことです。そのための材料がtailwind.config.jsに書かれた設定というわけです。公式サイトでこのようにデザインシステムが言及されていることから分かるように、デザインシステムを表現することがTailwindの主要なユースケースです。

CSSフレームワークの特徴

Tailwindによって生成されたCSSフレームワークの特徴は、ユーティリティファーストです。今回これが意味するところは、Tailwindは単一の役割を持ったクラス名を大量に生成するということです。

次のコードはTailwindを使用するマークアップの例です。

<div class="w-96 h-64 flex justify-center bg-gray-50">
  <p class="font-serif text-xl">Hello, world!</p>
</div>

このマークアップに対して適用されるCSSは、(Tailwindのデフォルトの設定の場合)以下のものに相当します。

div {
  width: 24rem;
  height: 16rem;
  display: flex;
  justify-content: center;
  background: rgb(249 250 251);
}

p {
  font-family: sans-serif;
  font-size: 1.25rem;
  line-height: 1.75rem;
}

このように、Tailwindを用いてスタイリングする場合、目的の要素に対して直接多くのクラス名を与える形をとります。多くの場合、1つのクラス名が1つの宣言に対応します。上の例ではtext-xlのみ2つの宣言を生み出しています(宣言は、CSS用語で プロパティ: 値; のことです)。

この例から分かるように、これらのクラスにはデザインシステムが組み込まれています。それは、w-96の96が24remを指していたり、text-xlのxlが1.25remを指していたりという点です。デザインシステムの素となる設定を変更すれば、96xlの意味を変えることができます。

他に、普通のCSSでは @mediaや擬似クラスを使わなければ書けないようなスタイルも、Tailwindではクラス名で書くことができます。例えばmd:w-48(画面の横幅が md 以上ならば w-48 を適用する)といったクラスや、hover:text-red-500(ホバー時のみ text-red-500 を適用)といったクラス名を使用できます。

メタフレームワークとしての特徴

whfont-text-といった類のクラス名一式は、Tailwind CSSにデフォルトで同梱されているcore pluginsに由来します。プラグインという名称から推察されるように、これらは無効化したり、また独自のプラグインを追加することで独自のクラス名を定義したりすることも可能です。このように、どのプラグインを選択するかにより、Tailwindが生み出す具体的なクラス名は完全に制御可能です。

このような「クラス名たちを生み出すフレームワーク」としてのTailwindの側面に着目すると、キーワードや数値でパラメトライズされたクラス名一式を生み出せる点や、それに加えてmd:hover:といったプレフィクスまで自動で面倒を見てくれる点が注目に値します。

尤も、筆者がこれまで見てきた限り、core pluginsを無効化して使っているケースはほとんど見たことがありません。設定をいじって自らのデザインシステムに合わせつつ、core pluginsを活用しているというケースがほとんどです。そのため、core pluginsによって生み出されるクラス名たちがTailwindの代名詞のように扱われているのが実情と思われます。

この記事でも、以降「Tailwindでは」という場合、特記がなければcore pluginsの使用を仮定します。

Tailwindは何でないか

ここまでTailwindの特徴を紹介しました。冒頭で述べたとおり、筆者の考えでは、Tailwindはあらゆる問題を解決してくれる万能のツールではありません。そこで、何がTailwindの領分であり、何がTailwindの領分ではないのかを分析します。

TailwindはCSSの代わりではないし、CSSの学習コストを減らさない

まず注意したいのは、TailwindはCSSの代わりではないということです。特に、「Tailwindを使えばCSSを覚えなくていい」ということはありません。Tailwindはあくまで(メタ)CSSフレームワーク、つまりCSSをうまく書くためのツールであって、CSSを書かなくていいツールではないということです。

Tailwindのクラス名を使用する場合、そのクラス名がどのようなCSSに翻訳されるのかを知っている必要があります。

例えばflexでFlexレイアウトに、gridでGridレイアウトにできますが、CSSにおいてFlexレイアウトやGridレイアウトがどのような挙動をするのか理解していなければ、これらは使いこなせません。

同様に、z-0z-10でz-indexプロパティを指定することができますが、z-indexもまたCSSに対する十分な理解がないと使いこなせないことで有名です。Tailwindを使用しても、その理解を省略することはできません。

学習コストの観点では、生のCSSの知識はどうせ必要ですから、Tailwindを使用しても学習コストはまったく減りません。Tailwindのクラス名は生のCSSに比べて簡潔なので一見すると簡単に見えますが、その簡単さはすでにCSSを理解している人が享受できるものであって、まだCSSを知らない人にとっては特に恩恵はありません。

また、すでにCSSを十分理解している人から見ると、Tailwindの(core pluginsによる)クラス名が短くて簡潔だからといって特に嬉しいわけではありません。エディタによる入力補完などが十分発達した現在では、Tailwindを使っても使わなくてもCSSの開発効率の差は正直ありません。むしろ、Tailwindのクラス名を理解するための追加の学習コストが必要なのでその分だけ不利です。プログラミングでは関数名や変数名を無理に短くすべきでないと言われていますが、それと同じく、Tailwindのクラス名はやや短くしすぎで、いわゆる「SimpleではなくEasy」なものになってしまっている印象を受けます。

バンドルサイズの方面から見ると、クラス名が短いことによるバンドルサイズ削減というのはあり得ないわけではありません。しかし、元々テキストは圧縮して配信されるものであるという点も考慮すると、よほどエクストリームなサイズ削減が必要でなければ誤差の範囲でしょう。

Tailwindは命名の苦しみからあなたを解放しない

TailwindのようなユーティリティファーストなCSSとよく対比されるのは「セマンティックなクラス名」です。これは次の例のように、セマンティックな意味を持ったクラス名(見た目ではなく内容を表すクラス名)を定義し、それに対してスタイルを定義するやり方です。

.user-profile {
  border-radius: 1rem;
  padding: 2rem;
  background-color: rgb(249 250 251);
}

.user-profile img.user-icon {
  width: 96px;
  height: 96px;
}
<div class="user-profile">
  <img class="user-icon" src="cat.jpeg">
  ...
</div>

この例ではuser-profileuser-iconといったものがセマンティックなクラス名です。

Tailwindに関して筆者が見かけたことがある誤解は、「Tailwindを使えばいちいち命名を考えなくてもよい」ということです。つまり、Tailwindのクラス名を使えばこのマークアップは次のようになり、user-profileuser-iconという名前が消えますね。

<div class="rounded-2xl p-8 bg-gray-50">
  <img class="w-24 h-24" src="cat.jpeg">
</div>

一見すると命名の面倒くささから解放されたように見えますが、それは幻想です。より実践的なシチュエーションにおいては、マークアップは正しくはこうなります(Reactで例示します)。

const UserProfile = ({ children }) => {
  return (
    <div className="rounded-2xl p-8 bg-gray-50">
      {children}
    </div>
  );
};

const UserIcon = ({ src }) => {
  return (
    <img className="w-24 h-24" src={src} />
  );
};

<UserProfile>
  <UserIcon src="cat.jpeg" />
</UserProfile>

つまり、結局マークアップは適切にコンポーネントに分割されなければならず、コンポーネントを命名する必要からは逃れられないということです。

Tailwind開発者による記事「CSS Utility Classes and "Separation of Concerns"」においても、ユーティリティファーストの良いところは「責務の分離」が達成できる点にあり、コンポーネントの概念は依然として必要であるとされています。この記事では上記のようなコンポーネントよりも複数のクラス名を合成した新たなクラス名を定義することが推奨されていますが、Tailwindのドキュメントでは上記のようなコンポーネントベースの方法も、UIライブラリを使用している場合に適した方法として紹介されています。

さらに言えば、Tailwindのようなユーティリティファーストなアプローチを使わなくても、ReactなどのUIライブラリを使っている時点ですでにクラス名の命名からはわれわれは解放されています。コンポーネントに対するCSSを書く場合、クラス名の名前空間はそのコンポーネントに制限されるのが一般的です。つまり、他のコンポーネントとの被りを気にする必要はありません。そのため、クラス名は wrapper とかそういう適当な名前でも大きな問題にはなりません。これは、プログラムで1行のコールバック関数なら引数にxとかそういう命名をしてしまうのと同じことです。

要するに、コンポーネントを作った時点で命名の問題はすでにコンポーネントのほうに移っており、これはTailwindとは無関係の出来事だということです。

本当に、Tailwindを使っていたとしてもコンポーネント分割はしっかり行なったほうがよいと筆者は考えています。1つのコンポーネントに属するマークアップの塊がでかいというのは1つの関数がでかいのと同じ状態であり、通常それは望ましくありません。また、より即物的な問題として、マークアップは普通のプログラムに比べてインデントが変動しやすい傾向にあり、スタイルが変更される際には大胆に修正されやすいため、破滅的なコンフリクトを生み出しやすいです。あらかじめコンポーネントに分割しておくことで破滅フラグを回避してください。

Tailwindの恩恵だがTailwindに特有ではないもの

Tailwindは複合的なソリューションです。つまり、1つの問題に1つの解決策を与えるピンポイントなツールではなく、複数の問題に対する解決策をある程度のオピニオンをもって統合した結果だということです。

そのため、Tailwindの特定の側面だけを求めて採用すると、他の側面に足をすくわれる可能性があり危険だと筆者は考えています。Tailwindというツールの全体を理解し、採用するかどうか決定しなければいけません。

そこで、Tailwindのさまざまな恩恵のうち、そこまでTailwindに特有ではない(これだけだとTailwindを採用する理由としては弱い)と筆者が考えているものを挙げます。

バンドルサイズ・ポータビリティ

Tailwindの特徴として、その出力がCSSファイルであることが挙げられます。そのCSSファイルを読み込むことで、Tailwindのクラス名がアプリケーションから使用可能になります。

この方式の主な利点は、JavaScript側(UIライブラリ側)と疎結合なところです。JavaScript側から依存するのはクラス名だけであり、UIフレームワークの取り換えなどが容易になります。

ところで、Tailwindは膨大な数のクラス名を用意するので、それらの定義を含むCSSファイルもまた非常に大きくなってしまうのではないかという懸念がありますね。

その対処として、Tailwindはバンドルサイズ最適化の機能を備えています。プロダクション向けの最適化をすることで、CSSファイルから実際には使われていないクラス名の定義が消されます。

この機構の強力な点は、クラス名をいろいろな場所で何回も使ったとしてもそのクラスの定義は1つしか存在しないということです。ある程度の規模のプロジェクトだと、ソースコード中のdisplay: flex;の数を数えると何百何千とあっても不思議ではありません。Tailwindの場合、実際に何百何千と繰り返されるのはflexというクラス名であり、display: flex;という定義は1箇所しかありません。

さらに、使っていないCSSを消し忘れてバンドルサイズが増えてしまうということは原理的に発生しません。最適化時にクラスが消されなかったということはマークアップの中でそのクラスの使用が実際に検出されたということだからです(多分検出は文字列検索ベースなので、場合によっては誤検知は発生します)。

加えて、Tailwind公式サイトの説明によれば、アプリケーション全体を賄うだけのスタイル定義を1つのCSSファイルにまとめても十分小さくなるとされています。これはユーティリティーファーストだけでなくデザインシステムの恩恵もあるのでしょう。これにより、CSSのcode splittingなど追加の施策が不要となります。これはモジュールバンドラの責務を簡単にしてくれるので評価しています1

ただし、これはあくまでも、Tailwindにおいてバンドルサイズが無駄に肥大化することはないということです。Tailwind以外のソリューションと比べたときに、バンドルサイズでどれくらい有利なのかはやや疑問があります。なぜなら、CSSのファイルサイズが削減された分のサイズはマークアップ側でクラス名を列挙するところに転換されているはずだからです。生のCSSよりもクラス名の方が短いですが、前述の通り文字列圧縮をすれば差は小さくなります。

vanilla-extractとの比較

この領域においては、vanilla-extractという競合がいることは指摘するに値します。書き味は次のような感じです(公式サイトから引用)。

import { style } from '@vanilla-extract/css';

export const className = style({
  display: 'flex',
  flexDirection: 'column',
  selectors: {
    '&:nth-child(2n)': {
      background: 'aliceblue'
    }
  },
  '@media': {
    'screen and (min-width: 768px)': {
      flexDirection: 'row'
    }
  }
});

Vanilla-extractは、インラインスタイルを採用している(クラス名を介さずに要素に対するスタイルを直接記述できる)という点と、アウトプットがCSSファイルであるという点がTailwindと共通しています。

一方で、ユーティリティークラスを使わずにオブジェクトの形でスタイルを記述するという点がTailwindと異なっています。CSSの構文ではなくTypeScriptとして書く点が少しエキゾチックですが、よく見るとCSSのプロパティ名や値を直接使用することができており、独自のクラス名という中間層がカットされています。

ポータビリティ、すなわち「アウトプットがCSSファイルである」ということを重要視する場合、Tailwindかvanilla-extractかで選択するのがよいでしょう。

Tailwindとvanilla-extractかを選択する基準となるのは、独自のクラス名という中間層が欲しいかどうかです。そして、この中間層の意義は、フレームワークにデザインシステムが含められる点です。

つまり、スタイルをデザインシステムで縛りたい場合はTailwindを使うべきであり、生のCSSを制限なく使用したい場合はvanilla-extractを使うべきです。

インラインスタイル

この記事ですでに触れているように、Tailwindでは独自のクラス名を定義することなく、divなどの要素に直接スタイルを与えることができます。そして、これはTailwindに特有の特徴ではありません。

インラインスタイルが目的の場合、Tailwindだけでなく次のような競合を考慮に入れるべきです。

生のstyle属性

<div style="width: 96px; height: 96px; display: flex; justify-content: center; background: rgb(249 250 251)">
  ...
</div>

生のstyle属性というのは何か馬鹿みたいですが、マークアップの意味としての本質はTailwindと何も変わっていません。小規模なプロジェクトなど場合によっては選択肢に挙がるでしょう。

生のstyle属性の欠点は、バンドルサイズが肥大化しやすい点や、メディアクエリ等を扱えない点です。この点が問題になる場合には下記の選択肢に発展します。

styled-components・emotion

Styled-componentsやemotionは、スタイルがバンドルされたReactコンポーネントを作れるライブラリです。一見するとTailwindとは毛色が違いますが、css propを使うとインラインスタイルに近い書き心地となり、メディアクエリ等も扱えます。

これを使う場合は次のように書けます。

<div css={`
  width: 96px;
  height: 96px;
  display: flex;
  justify-content: center;
  background: rgb(249 250 251);
`}>
  ...
</div>

この系統のライブラリはTailwindとは毛色が結構異なり、Babel(またはSWC)の適切な設定が必要だったりSSRが面倒だったりするなどやや難しい点もありますが、書き心地のいいインラインスタイルが欲しいということであれば選択肢に挙がります。

vanilla-extract

さっき紹介してしまいましたが、vanilla-extractもインラインスタイルを可能にするライブラリです。

Tailwindの本質と注意点

では、Tailwindを採用する理由となりうるのはどのような点でしょうか。筆者の考えでは、それはメタフレームワークとしてのTailwindの恩恵を受けたい場合です。

つまり、デザインシステムに応じたクラス名のセットを生成してくれるフレークワークであるということ、この点に魅力を感じるのであればTailwindを採用する理由となります。

Tailwindとデザインシステム

筆者はデザインにそこまで詳しいわけではありませんが、フロントエンドをやる以上デザインについて考えないわけにはいきません。もしあなたが担当プロジェクトのデザインについてまだ考えていないのであれば、Tailwindを採用するという判断は時期尚早であると言わざるを得ません。

そもそも、直接スタイルを書かずにTailwindが生成したクラス名を使うということは、CSSのフルパワーを出させてもらえないということです。言い方を変えれば、Tailwindはスタイルに制限をかけるということです。

制限をかけるということ自体は必ずしも悪いことではありません。我々は普段から、ESLintなどを用いて嬉々としてプログラムに制限をかけています。デザインの場合、使える色を特定のパレットに制限するとか、長さを8pxの倍数に制限するとかして、統一感のないデザインが作られてUXの一貫性が崩れてしまうことを防ぐことができます。

これは、まさにデザインシステムが目指すところです(筆者のデザインシステムに対する理解では)。つまり、「Tailwindを導入する」ことはもはや「デザインシステムを導入する」ことに本質的に等しいということです。このことから明らかなように、デザインシステムを入れるつもりが無いのであればTailwindを入れるべきではありません。

ただし、「デザイナーがコンポーネントをたくさん定義してくれた」とか、その程度ではここでいうデザインシステムには入らないので注意してください。デザインがついたコンポーネントを実装するくらいなら、どんなCSSライブラリを使ってもできます。この記事の前半で議論したように、UIフレームワークを使っている場合、コンポーネント化はCSSライブラリではなくUIフレームワークの領分です。

Tailwindの恩恵を受けられるようなデザインシステムというのは、アプリケーションの隅から隅まで、コンポーネントの色や大きさからコンポーネント間の隙間に至るまで、あらゆるスタイルがルールに縛られているようなものです。

ここまで来ると、デザインシステムのルールを全てコンポーネントベースで表現するのが辛くなってくるでしょう。Tailwindが採用しているユーティリティファーストは、隅々までデザインシステムのルールを行き渡らせるのに適しています。

制限のかけ方は、大まかに許可リスト方式と拒否リスト方式があると考えられます。前者は、できることのセットを用意してその中でやってもらうことを指し、後者はできないことを指定して弾く仕組みです。リンター等は拒否リスト方式である一方、Tailwindは許可リストです。

デザインシステムにおいては、許可リスト方式のほうが望ましいと筆者は考えています。CSSの表現力があまりに高く、拒否リスト形式で望ましくないデザインを的確に弾くのが非常に困難だからです。

tailwind.config.jsという砦

Tailwindでは、利用できるクラス名はtailwind.config.jsをもとにして生成されます。どのようなクラス名が利用できるのかによってデザインシステムが決定づけられることを踏まえると、実質的にデザインシステムを定義しているのはtailwind.config.jsであるということになります。

つまり、tailwind.config.jsに変更を加えることはデザインシステムに変更を加えるのに等しい行為です。

これが意味するのは、tailwind.config.jsに気軽に変更を加えられるような運用にすべきではなく、そのような運用はTailwindを活かせていないということです。明確な目的意識をもってTailwindを導入したのであれば、tailwind.config.jsに変更を加えることにはすべからく慎重になるべきです。

ESLintに置き換えて考えてみれば、誰でも自由に設定を変更できるというのはおかしいということが分かるはずです。Tailwindの設定ファイルもまた、そのように取り扱わなければいけません。

もしこのことを意識しないままTailwindを使っていた/使おうとしたのであれば、おそらくTailwindはあなたのやりたいことに適していません。他の選択肢を考えた方がよいと思います。

任意の値を使えるアレ

ここまで説明してきた通り、TailwindはCSSでできることを制限してデザインシステムを担保するためのものです。制限のひとつが、長さ等として使える値の制限です。例えば、デフォルトの設定ではwidthを設定するためのクラス名はw-16, w-20, w-24, ……といった具合であり、w-18という中途半端な値は存在しません。これによって、使うべきでない長さがCSSで使われるのを防いでいます。

一方で、いつぞやのバージョンアップで、w-[57px]のように、[ ]構文を用いることで制限に縛られない自由な値を使える機能が追加されました。これは明らかに大きな抜け穴であり、これまで説明してきたTailwindの意義に真っ向から反しています。筆者もこれを知ったとき、Tailwindがどのような方向性を目指しているのかよく分からなくなりました。

Tailwindをデザインシステムのために使っている限り、この機能は明らかに濫用すべきではありません。TypeScriptで言うところのanyasに相当するものです。

この機能はTailwindがわざわざ提供してくれた「不便さ」を覆して「便利」にスタイルを書けるようにしてくれるものですが、だからといってTailwind以外に比べてなお便利になるわけではなく、マイナスを0に戻しただけです。この機能をフル活用する場合はもはやTailwindの特徴が「クラス名が短い」くらいしか無くなってしまいます。記事の前半で述べたように、筆者の考えでは短いことは別に良いことではないので、この機能を使う意味がありません。

たまに使い道があるかもしれないとはいえ、リンターとかでデフォルトは禁止にしておきたい機能ですね。みなさんも注意しましょう。

Tailwindはどれくらい効果があるのか

前述のように、Tailwindは制限を通じてデザインシステムの遵守を担保する効果があります。Tailwindを使ってスタイリングすることで、要素間のマージンひとつに至るまでデザインシステムのルールが行きわたります。

また、フロントエンドに携わるエンジニアの間では、デザイナーが作ったデザインカンプは1px単位で忠実に再現するためのものではないという説が人気です。その場合に、Tailwindからデザインカンプから実際の実装に起こす際にデザインの端整さが損なわれるのを避けることができます。

一方で、Tailwindに従っているからといって何をしてもOKというわけではありません。ボタンとボタンの間隔が意味もなくm-2だったりm-3だったりしたら、おそらく良いデザインにはならないでしょう。

このことから、デザインに関してはTailwindに任せておけば心配なしというわけではありません。デザイナーが居るにせよ居ないにせよ、人間の英知が依然として必要です。

そのため、筆者の考えではTailwindはデザインシステムの推進という面においてそこまで強力ではなく、控えめなリンターのような印象です。意外とストライクゾーンが狭く、Tailwindがしっかりとハマるプロジェクトというのは実はめったにないのではないかという気がします。それでも、無いよりはマシで生産性が上がりそうとも思っています。

まとめ

この記事全体を通して主張してきたように、Tailwindの本質的な利点はメタCSSフレームワークであるという点であり、デザインシステムに由来する制限をスタイリングにもたらすことができるという点です。デザインシステムを強制するという観点からは、Tailwindの手法は筋がいいと筆者は考えています。

それ以外の点(バンドルサイズが小さいとか、クラス名が短いとか)は代替の手段があったり、そもそも利点ではなかったりするため、そちらを目的にしてTailwindを採用すべきではありません。そうしてしまうと、Tailwind特有の制限やオーバーヘッドがわずらわしく感じるでしょう。

結論として、Tailwindの採用を検討してもいいと筆者が考える場合を再掲します。

  • デザインにこだわりがなく、最低限整っていればいい場合。デザイナー不在のプロジェクトなど、デザイン上の細かな要求が無いがとりあえず整えておきたいという場合は採用してもよいかもしれません。Bootstrap的な立ち位置のものが欲しい場合です。
  • 逆に、デザインシステムをしっかりと整備しそれからの逸脱を防ぎたい場合。ただデザインがあるだけでなく、フロントエンドエンジニアまで巻き込んでデザインシステムを整備したいということが重要です。このようなユースケースではTailwind CSSのアプローチが適している可能性があります。

追記

この記事はけっこう反応をいただいたので、たしかにと思った点について追記します。

命名の苦しみについて

とくに異論が多かったのは命名の苦しみに関する点です。本文で次のようなコードを例示しましたが、「1要素1コンポーネントにはしないので、コンポーネントの中でクラス名を命名する手間が省けている」という反論が多くありました。

const UserProfile = ({ children }) => {
  return (
    <div className="rounded-2xl p-8 bg-gray-50">
      {children}
    </div>
  );
};

const UserIcon = ({ src }) => {
  return (
    <img className="w-24 h-24" src={src} />
  );
};

<UserProfile>
  <UserIcon src="cat.jpeg" />
</UserProfile>

つまり、実際には上記のようではなく下記のようなコンポーネントになることが多く、その場合divとimgに対してスタイルを充てる必要があるということです。

const UserProfile = ({ iconSrc }) => {
  return (
    <div className="rounded-2xl p-8 bg-gray-50">
      <img className="w-24 h-24" src={iconSrc} />
    </div>
  );
};

<UserProfile iconSrc="cat.jpeg" />

これについては筆者も同意するところで、筆者が実際に実装するならこのようにdivとimgを1つのコンポーネントにまとめます。インラインスタイルを使わなければ次のような感じになるでしょう(wrappericonという2つのクラス名を命名する必要が出てきています)。

const UserProfile = ({ iconSrc }) => {
  return (
    <div className={classes.wrapper}>
      <img className={classes.icon} src={iconSrc} />
    </div>
  );
};

なので、本文の構成は最適な説明ではありませんでした。素直に反省します。

こうなった理由としては、複数の論点を混ぜるのを避けたかったという点と、筆者がwrappericonといったクラス名を用意することをまったく苦に感じていなかった(wrappericonという名前を考えることを省略できる程度では利点にならないと思った)という点があります2

ポイントは、本文で説明している通り、クラス名はコンポーネントの名前空間に閉じるので、そのコンポーネント内で意味が通じればよいということです。そのため、クラス名はuser-wrapperuser-iconよりはwrappericonとします。

上のコンポーネントなら、筆者はwrappericonといった命名を2秒くらいで考えるので、そこを省略できることにあまり意味を感じていません。また、「コンポーネント名はあとから名前を変更しやすい一方でクラス名は変更しにくい」という意見も見られましたが、クラス名がコンポーネントの名前空間に閉じていれば名前の変更は十分に可能です。

もしコンポーネント内のクラス名を2秒で考えられないくらいコンポーネントが複雑化したら、そのときがコンポーネントの分けどきだというのが筆者の考えです。「outer-wrapper, wrapper, inner-wrapperができた」みたいな笑い話も耳にしますが(筆者も実はやったことがありますが)、そういう構成が発生するのはCSS上の都合であることが多いので、そのレイアウトを担当するコンポーネントを分離して役割に応じた名前を付けたほうがよいです(flex-container, itemとか)。

とはいえ、実際に記事の反応を見てみると、wrapperというクラス名ひとつすら付けたくないという意見が結構見かけられました。そのような方にとっては、確かにクラス名がないことは利点になるかなと思います。そのような方にとっては「命名の苦しみからあなたを解放しない」は言いすぎで、ゼロにはなりませんが苦しみを削減できるという利点があるでしょう。

ただし、一点注意しておきたいのは、クラス名を書かなくていいのはインラインスタイル全般の利点であって、Tailwindに特有ではないということです。Tailwindを採用する理由になるべきなのはデザインシステムであるという主張は変わっていません。

なお、命名に関しては記事を書いてくださった方もいます。こちらもぜひご参照ください。

JavaScriptがない場合について

上の話もそうですが、UIライブラリなどを用いてコンポーネント化できることが前提の議論となっています。

そうなると、ちょっとしたLPを作成する場合や完全なMPAの場合など、そもそもUIライブラリを使わないケースでTailwindの利点が出るのではないかという意見もありました。

このケースはこの記事の考慮から漏れていたので、普通にそうだと思いました。グローバルにクラス名の管理をするのが辛いのは歴史が証明しており、JavaScriptを介さない方法の中ではTailwindが有力な選択肢となると思います。Vanilla-extractとも差別化できています。


  1. ただし、この方向性が唯一のアンサーであるとは思っていません。短期的にはモジュールバンドラがすでにCSSをうまく扱ってくれているし、中長期的にはCSSもES Modulesのセマンティクスの上に載せられる流れが見えているからです。
  2. お前じゃなくてチームの生産性のことを考えろという意見もあるかもしれませんが、命名はクラス名にかかわらずエンジニアリングに必要なスキルなので、この程度のスコープの話であれば携わる人の能力を伸ばすほうに導いたほうが賢明だと思います。