「Tailwind CSSめっちゃ負債になりそう」はそうでもないのでは、と思っている

Tailwind CSS 1 を一目見た人、特にCSS初学者のうちけっこうな割合が「これエグい負債になりそう」と思う気がする。なぜなら実際にそのような意見をちらほら見るからなんだけども、自分はあんまりそうは思っていないし、微妙に今のCSSについて誤解があるような空気も感じるのでその理由を説明したい2。JSXと同じで嬉しさを理解して使い慣れればなんてことはないのだけど、一方でその背景にある話はJSXより複雑なので単純に使って慣れればいいという話でもなさそう。

なお、この記事は私の以下の2ツイートを膨らませたものです。

Tailwind CSS、剥がすのは大変そうだけどそれをもって重大な負債になると評せるかは微妙に思っている
https://x.com/aumy_f/status/1822094147853209734

コードベース上のスタイル記述方法が素のCSSだったとしても、CSS modulesやstyled-components、vanilla-extractなど、スタイルの塊に名前を付けた時点で具体的スタイルとDOM要素の間には1枚の抽象化層が挟まっていて、そいつが腐る可能性はつねにあるんだよな
https://x.com/aumy_f/status/1822096999044505890

ところで予防線を張るのを許してほしいのだが、自分は業務でTailwind CSSを利用したことはないので、実際にはメンテエグいキツみたいな話はありえるかもしれない。

負債の定義

技術的負債の原典を引いたりするのはめんどくさいが、ここで負債とは何か明確にしないとめちゃくちゃ雑な議論になってしまうのでとりあえず定義をしてやや雑な議論ぐらいにしておく。技術的負債とは変更を妨げるものである。たとえばタイトルの文字を黒から赤にしたいとき他の部分の色が変わってしまわないか気にする必要があったりすると負債がでかいことになる。

「マークアップとスタイルの分離」は幻想

いきなり強めの表現なので申し訳ない気持ちでいっぱいだが、これは言っておきたい。けっこう「マークアップとスタイルの分離」の観点からTailwindに難色を示す人がいるようだが、どうがんばってもスタイリングのためのマークアップは避けられず、複雑化したセレクタの詳細度はパズルとなり、かつて言われた「HTMLとCSSを分けるべき」という指針はいまや幻想だと言わざるをえない。

全セレクタが巨大なマークアップ全体を射程に収めているスタイルシートというのはどのスタイルがどこに影響するのか追跡するのに限界があり、BEM、CSS Modules、CSS in JS、Vue/Svelte SFCのscoped CSSなど、小さなマークアップとそれに密結合したスタイルシートという構成が長いこととられてきた(『Tailwind CSS実践入門』p9–も参照)。実際デプロイされるアプリケーションは巨大なマークアップとグローバルにセレクタ名前空間を共有するスタイルシートで構成されているかもしれない3が、コードを書くときの気持ちとしてはずいぶん昔から「小さなマークアップとそれに密結合したスタイルシート」なのだ。

Tailwind CSSはマークアップ内にスタイルを書かせることで、この「小さなマークアップとそれに密結合したスタイルシート」というモデルを字面上でも実現する。実際に密結合なものは字面でも近いほうがいい、という原則はコロケーションという名前で知られている。

字面がCSSではないことが重要だろうか

ご存じのようにTailwind CSSを使ったコードベースにおいてスタイルは以下のように書かれる。

<div class="flex gap-4 items-center">子要素略</div>

当然ながら flex gap-4 items-center という文字列は妥当なCSSルールではないが、これが素のCSSではないために将来に負債になりそうという意見を述べる人がそれなりにいるように思う。Tailwind CSSからCSS Modulesのような素のCSSを書く手法に移行するためには、flex gap-4 items-center を素のCSSに変換する必要があり、これはstyled-componentsのような同じく素のCSSを書く手法ならだいたいコピペで済むことと比較してあきらかに手間がかかりそうである。

深刻な前例としてSass(SCSS)やStylusといったCSSプリプロセッサを想起する人もいるかもしれない。彼らは素のCSSとは異なる文法や機能(変数とかmixinとか)を持っており、剥がす際にはそれらをなんとかする必要がある。

しかし、自分の意見としては、構文だけを見て「これは標準準拠でないから剥がしづらく、負債になる」と考えるのは少し違う と思う。CSSプリプロセッサはコンパイル時に展開される変数やmixinといったWeb標準にない独自の機能が移行の難易度を上げているのであって、Tailwind CSSの特異なスタイル記法はものすごく剥がしづらいとはいえない4と主張したい。Tailwindのクラスは基本CSSプロパティと1:1対応するものなので素のCSSに書き換えるときも負荷はそれほど高くなく、ある程度機械的に変換することも可能だろう。

そして、いざというとき剥がすのが大変だから負債になってよくないというのも理屈として微妙とも思う。技術を剥がしたくなる大きな要因として日ごろのアプリケーションへの変更を阻害しているというのがあり、Tailwindはいざというとき剥がすのがやや大変かもしれないが、どんな技術も剥がすのはそれなりに大変であり、日頃のアプリケーションへの変更はむしろかなりやりやすくなる技術なので十分リターンが得られるのではないだろうか。

一応、Tailwindから脱出せざるを得ないシナリオの1つとしてTailwindの終了というものがあると思うが、Tailwindは多数の企業に利用され、すでにそうそう死なない5フェーズに入ったと自分は見ている。また究極的には flex gap-4 items-center はただのクラスであるため、各クラスに対応するスタイルを別途用意すればコードベースを維持しつつTailwindを剥がすこともできるとは思う。このあたりがChakra UIなどとの差別化要素になるだろう。

そもそも既存のCSSアプローチには腐る余地がある

BEM、CSS Modules、styled-components、vanilla-extractなど既存のほとんどの手法はスタイルの塊に(多くの場合、セマンティックな—それがなんであるかではなく、どこに使われるかに着目した—)名前をつける。

CSS Modulesの例:

.actionList {
  display: flex;
  gap: 16px;
  align-items: center;
}

vanilla-extractの例:

export const actionList = css({
  display: "flex",
  gap: "16px",
  alignItems: "center",
});

displaygapalign-items、この3つの宣言は「交差軸の中心に隙間16pxでアイテムを配置するflexbox」ということだけを示している。波括弧の中だけ見ればこいつらがアクションリスト(ボタンとかが並んでるんだろう、たぶん)のスタイルであるとはわからない。これがアクションリストのスタイルであるとわかるのは、こいつらをまとめたものに変数名あるいはクラス名として actionList が与えられているからだ。

この「スタイルの塊に名前をつける」という行為は抽象化の層を一段増やしている。主語のデカい私見だが、いかなる抽象化層も潜在的に技術的負債になりえる。こいつは actionList と名付けられているが将来にわたってその名前が適切であり続ける保証はない。actionListInner とか actionListBody が適切な名前になるのかもしれない。そのたびに我々は適切な名づけを考えることになる6

その点でTailwind CSSの flex gap-4 items-center という文字列は(デフォルト設定で解釈すると)「交差軸の中心に隙間16pxでアイテムを配置するflexbox」というそのままの意味しか持っていない。なんなら <div class="flex gap-4 items-center"> というHTML片のレベルで見てもこいつは「交差軸の中心に隙間16pxでアイテムを配置するflexboxなdiv」であってアクションリストだとはどこにも書いていない、というか書く必要がない。この特性は、従来の手法なら actionListWrapper とか actionListLabelHelpLink みたいなクラスの名づけが必要になるレベルでレイアウトが複雑化していくほど負荷を減らしていく7

ユーティリティクラスはイミュータブル

Tailwindが使っているユーティリティクラスだってクラスに名前をつけているから抽象化であって負債のもとになるのではないか?という疑問があり得ると思うが、それに回答しておく。

従来のCSS手法を使うコードベースでアクションリストの背景を黒くしたいとき、あなたは actionList クラスのスタイルに background-color: black; を足すだろう。一方ユーティリティクラスの場合は class 属性に bg-black を足す。このとき従来のCSS手法では変更を加えるのは actionList クラスのスタイルの中身である。変更の前後で同じクラスだが、スタイルの内容が変わっている。一方でユーティリティクラスの場合、変えるのは class 属性の文字列であってクラスそのものではない8。このことはつまり、(仕様が固まった)ユーティリティクラスはアプリケーションレベルの変更についてイミュータブル9であるといえる。

イミュータブルなクラスでスタイルを構成していると、クラスの中身が何らかの変更で書き換わってしまい意図しないスタイル変動が起こる、といったことが起きない。1クラスにつき1回のみの使用を徹底すれば同様の効果は得られるが、やや労力がいる10。ある程度盤石なパーツを組み合わせていくのはソフトウェア開発の重要なプラクティスの1つだ。

インラインスタイル系

style 属性によるインラインスタイルは疑似要素などに対応しておらず明らかに機能不足だが、Emotionのような css タグ付きテンプレートリテラルでCSSを書く手法を持ち出して比較するのは価値がある。ざっくりTailwindの利点を説明すると「短い」「とりうる値の種類を制限できる」の2つになる。やや脱線するので「Tailwind デザイントークン」とかで検索してほしい。

この記事でおもに説明しているTailwindのいいところである「名前付けからの解放」についてはインラインスタイル系でもだいたい同じ議論が通用するだろう。ここだけに着目し、素のCSSであることによる将来的移行性を見据えるならTailwindでなくてもいいが、デザイントークン的な話も面白いのでぜひ見てみてほしい。

Preflight

Tailwind CSSはReset CSSとしてmodern-normalizeを採用し、さらにその上にPreflightというかなりopinionatedなスタイル群を適用する。こいつに依存するのがけっこう将来的な脱出で足枷になる可能性はあると思うが、こういう系のベーススタイルはなに選んでも心中みたいなとこあるので受け入れるしかない気がする。

TailwindとCSS入門の難しさ

とまあこのようにTailwind CSSのよさを語ってきたわけだけども、Tailwind CSSはBootstrapなどいい感じなありもののスタイルがあるようなフレームワークとは異なり、CSSプロパティを理解し操ってレイアウトを組み見た目を作る必要があるので、少なくとも普通にCSSを書けるだけのスキルは要求される。

それなりにCSSを書いていると最終的にTailwindええっすわ…になりがちと言われている(要出典)のだが、逆にコンポーネントベースCSSを枷と感じていない段階の入門者が同じような理解に至るのは難しく、CSSのカスケーディングの難しさとTailwindの特異さで右も左も意味不明な技術ばっかや!という気持ちになってしまうのは仕方ないところがある。まあ手続き的DOM操作のつらさを味わわずにReact直行する人(おれとかわりとそう)がいっぱいいるだろう昨今を考えればガチ最低限のCSS知識つけたうえでTailwind直行しても別に問題はないのかもしれない。

ところでCSSはヤバとかCSSは世界最難のプログラミング言語とか冗談めかしていわれているわけだが、その難しさにはインタラクティブなビジュアルを作るのは本質的に難しく、CSSを滅ぼしてもおそらく人類は同じ困難にぶち当たるというものが含まれているのでそのへんは認識してもらえるとうれしい気持ちがあります。

Tailwindを入れるべきでない状況

わりとあると思う。パッと思いつくのは制作と以後のメンテで担当が分かれててメンテ担当にビルドツールを扱える技量を期待できないとき。あとTailwindはどちらかというとアプリケーション向けの技術で、そこら中に複雑なレイアウトやアニメーションが登場するようなクリエイティブ寄りのWeb制作物だと微妙そう。『Tailwind考』などを参照されてもいいかもしれない。

Tailwindが負債になるのはエディタのTailwindプラグインのメンテが滞った時だと思う

これは大マジ。実際Tailwind Labs(=公式)がメンテしてるのでそうそう起きないとは思うが、Tailwindの体験の8割はエディタプラグインに支えられていて、前UnoCSSちょっと触ったときもエディタプラグインの挙動が合わなくて挫折した。逆にプラグインまだ入れてない人は今すぐ入れてください。

「クラス名の命名規則が謎すぎ」

これは大マジ。align-itemsalign- (これは vertical-align) じゃなくて items- なのとか。長さとの兼ね合いではあるんでしょうが。

おすすめ文献

コメント返しのコーナー

なるほどな~と思った順でソートしてあります。

  • 「いかなる抽象化層も潜在的に技術的負債になりえる」について、プログラムの関数についてもその主張が成り立つだろうか?ほんまか?
    • 俺はホンマだと思う、不適切に命名されて共通化された関数は全然変更を阻害する。まあたとえば配列の総和とかmapみたいな定番の関数はユーティリティクラスと同じようなものでそうそう変更されないからほぼほぼ負債にならないが、あくまでそういう関数もあるというだけの話。
  • Tailwindだと(Playwrightみたいなブラウザテスト、ユーザースタイルシートなどで)セレクタによる要素の特定が困難
    • これはわかる。Tailwindに限らずCSS Modulesなど自動生成系のアプローチでもこれに引っかかる。この記事はどちらかというとCSS初学者向けに書かれているのでこのようなトピックには触れていないが、セマンティックなクラス名に慣れ親しんだ人もターゲットに含めるなら言及してもよかったかも。自分は data- 属性しか思いつかないんですがいい手法ありますか?
      • 追記…aria とかか
  • 「似た部品で細部だけ違うヤツが大量発生すると辛そうな印象あるけど、細部だけ変えるとかは無しの方向にしてるのかな?デザイナーのセンスを殺さないと導入難しい気がしてる。」
    • ユーティリティファーストでやるとスタイルをコンポーネントとして切り出すことでの共通化が遅延されるので、むしろ似た部品で細部だけ違うヤツの扱いは(コピペに抵抗がなければ)楽なほうなのではないかと思っている。
  • デザインカンプとのピクセルレベル完全一致を求められる意味不明な業界だと合わない
    • つ、つらすぎる!!!!!!!!!
  • cssはスコープが不意に広がりすぎる事を止める事が出来ないのがつらすぎる。だからhtmlコンポーネントがもっともっともっと普及してスコープを区切るのが当たり前になったら不要になる!後10年くらい?
    • 記事の内容に嚙み合っているか微妙なコメントなので触れるか迷ったが一応、HTMLコンポーネントというのはWeb Components(特にShadow DOM)のことだと思うんだけど、Shadow DOMはセレクタを分けるとかではなくあらゆるスタイルが隔離されるので、アプリケーションを構築する一部としてのコンポーネントに利用するのはやや過剰なところがあると思う。
  • 「慣れれば」って時点で負債の発生源になってると思う
    • どんな技術も最初は慣れる過程が存在する、構造化プログラミングも純粋関数もADTもモナドも嬉しさを理解して使って慣れれば自然なことだ。実際には抽象度が高すぎて人によって慣れるのが難しいから新規メンバーが触れなくなりがちな概念とかあるが、「慣れれば」という技術がなんでも負債の発生源になるとは思わない11し、Tailwindについてはそのようなレベルで抽象度が高いとか筋が悪いとか誰も使ってないとも思っていない。
  • もう何か議論する対象でもなく当たり前に使ってるもの。Tailwindを理解できない個人が何を言おうと世の中の方が先に変わった。技術の世界は常に多数派が勝つ。
    • ところで、こういう系の(まだ)わかってない人を突き放すようなことを言うのは新規参入の障壁になるので好ましくないと思う。わかってない人がデカい声でしゃべっているのを見るとむかつくみたいな心理はわかるが。
  • Tailwindを擁護する記事の7割くらいは「僕はCSSを書くセンスがない」とか「私は文書の構造を考えることができない」とかを長々と書いている(私見)
    • 「私は文書の構造を考えることができない」というより「俺はスタイルの都合に引っ張られるマークアップの構造にいちいち意味ありげな名前をつけるのをナンセンスだと思っている」が正しい。
    • つか「僕はCSSを書くセンスがない」って技術Aの難しいところに手を入れる技術ならなんでも「なんとか言ってるけどこいつにはAを扱うセンスがないだけでしょ?」といって難癖できるのでセコくないか?
  • Reactのアーキテクチャが時代遅れだからTailwind使わざるを得ないだけ。Scoped CSS+SFCのVueやSvelteはTailwind不要。
    • 話聞いてた?
  • フロントエンドはトレンド変化が早すぎる
    • Webフロントエンドの記事がちょっと話題になるとぬるぽにガッがつくレベルで決まってこのブコメがつくけど、俺はこの種のコメントがたいへん気にくわない。

Footnotes

  1. 以下、気分によってTailwindと略す。正式名称ではないが、公式ドキュメントでも使われている略し方である

  2. この記事には断定形が全然ないが、がまんしてほしい。

  3. BEMは特に見かけ上直接「巨大なマークアップとグローバルにセレクタ名前空間を共有するスタイルシート」を書いているが、これも実際に規約を運用するときのメンタルモデルとしてはコンポーネントという細切れのマークアップの存在に基づいている。

  4. 脱線する話をすると自分はたまに見かけるWeb標準への過剰な信仰はあまりよくないという意見を持っている。本当にWeb標準に準拠していないというのはAdobe FlashとかSilverlightのような勝手拡張で造営されたインフラやそれに依存する物体であって、その視点でいえば現在のWebブラウザ開発でわれわれ依存するのはすでに確立されてそうそう廃止されないWeb標準であり、比較的長期の寿命が期待できる。Flashのようなオレオレインフラが消え去ったからか「Web標準に準拠」の意味が「Web標準の構文やAPIを使用」に変容しつつあるように思う。それ自体は悪くない話だが、Flashに向けていた憎悪がそのままWeb標準でない構文やAPIに向けられてはいないか、ということはたまに考える。ブラウザAPIとの字面の近さでAxiosとFetch APIを評価するのがはたして正しいことなのだろうか?

  5. 有料化したらウケるが、どちらかといえばそっちのほうがありえる

  6. ちょっとこのへん具体例出せるとうれしいが、余力がない

  7. 結局(たとえば)Reactコンポーネントという形で名前をつけることにはなるのではないか、という疑問にここで回答しておくと、コンポーネントを構成するすべてのスタイルの塊に名前をつけなければならない状況と、必要なときに一定のスタイルの塊に名前をつけることができる状況は異なると考える。

  8. 本当に一応の確認なんですけど、ユーティリティクラスを使う場合、text-4xl (たとえば font-size: 2.25rem) が当たっている要素のフォントサイズを1.5remにしたいときにやることは text-4xltext-2xl に替えることであって、text-4xl の定義を font-size: 1.5rem に編集することではない。

  9. いきなり独自な言い回しを持ち出して申し訳ないんだけど、要するに「アプリケーションへの変更(たとえば、アクションリストの背景を赤くしたい)をするためにユーティリティクラスの中身を変更する必要はない」ぐらいの意味合い

  10. マジで関係ないんだけどこれshared XOR mutableっぽくておもろい

  11. やや屁理屈めいた脚注…あらゆる技術は潜在的に負債なので慣れれば便利な技術も全部潜在的に負債という考え方ならわかるが、『「慣れれば」って時点で負債の発生源になってる』という文章の意味としてはそういうことではないだろう