Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: Tailwind CSSはコンポーネントに向いているCSSか?

皆さん、Tailwind CSS(以後Tailwindと書きます)、使ってますか?

参考: Tailwind CSS - Rapidly build modern websites without ever leaving your HTML.

tailwindlabs/tailwindcss - GitHub

私はオレオレRailsアプリで数年前にノリ一発でViewComponentとTailwindを導入したはいいものの、Tailwindのコンセプトの理解が不十分だったため、ViewComponentとどう溶け込ませるかで、ああでもないこうでもないと相当悩んでしまいました🫠

個人的には、Tailwindのカラー設計の美しさに惚れ込んだのがきっかけとして大きかったと思います。個人的に#00ffffのシアンという色が大嫌いで、Windowsのデフォルト色として見かけるたびにがっかりしていたのですが、Tailwindのシアンは私の先入観を覆してくれました❤️。


Colors - Core concepts - Tailwind CSSより

また、Tailwindの命名規則を見ていてどことなくRISC CPUのインストラクションにも似たシンメトリーを感じて、そこが好きになったのだと思います。


flex-basis - Flexbox & Grid - Tailwind CSSより

参考: 命令セット - Wikipedia

🔗 一般的なTailwindの印象と私の印象

  • ⭕️ 「ユーティリティクラスが多数定義されていて、自分でクラス名を考えないで済むのがいいよね」
  • ❌ 「style=""属性に書いているのと大差ないんじゃないの?」

前者は私の第一印象でもあります。しかしstyle=""とどう違うのかと言われても長らく答えられない有り様でした。

  • ⭕️「ユーティリティクラスの命名規則がシンメトリックかつ一貫していて覚えやすいよね」
  • ❌「また別のCSS記法を覚えないといけないの?」

私も長らく知りませんでしたが、ユーティリティファーストを採用しているCSSシステムはTailwind CSS以外にもあります。

なお、既存のCSSシステムからTailwindに移行するのは変更量が多すぎるので、よほどの理由がなければ既存のものを移行する意味はなさそうです。Tailwindを導入するのであれば、新規プロジェクトのなるべく初期段階がよいと思います。

🔗 Tailwindは何を目指しているのか

Tailwindの公式ドキュメントを読んでも今ひとつピンとこないままだったのですが、RailsでTailwind入りのコンポーネントを作ったり壊したりしながら、ChatGPTなどのAIと質疑応答を繰り返すうちに、やっとTailwindのやりたいことが見えてきました。ReactのコンポーネントでTailwindを使っている人たちからすると今さらかもしれませんが😅

🔗 1: すべてをユーティリティクラス化したのは「詳細度を揃えて制御可能にする」ため

詳細度(specificity)は、CSSの複雑さとつらみをもたらすものとして有名かと思います。

参考: 詳細度 - CSS | MDN

【社内勉強会】特濃!CSS講座 #2: セレクタ、カスケード、継承をがっつり理解する

詳しくは上の記事に譲りますが、詳細度(specificity)の算出は複雑で、セレクタやカスケードなど周りのさまざまな文脈ですぐ変わるので、それに足を取られてCSSが思うように効かないという話はいくらでもあります。

そして!importantという怨霊退散の御札を貼って強引に言うことを聞かせようとすると、今度はたちまち!importantだらけになってしまったりします。誰もが通る道かもしれません。

Tailwindがすべてをユーティリティクラス化したのは、単にシンメトリックなクラス名にリネームするだけではなく、それによってユーティリティクラスの詳細度を揃える、そして詳細度への影響をできるだけ小さくすることが重要だと理解しています。

🔗 2: @themeでデザイントークンを設定し、3つの@layerで優先順位を制御する

そのうえで、Tailwindには以下のようなしくみがあります。

@theme変数はTailwind固有の拡張です。@themeは優先順位の階層には含まれず、Tailwindにデフォルトの設定(フォントやカラーパレット、ブレークポイントなど)を上書きしたり、カラーパレットに色を追加したりできます。

  • @theme {}
    カラーパレット、基本フォント、ブレークポイントなどのデザインシステム(デザイントークン)はここに配置する

そのうえで、Tailwindは以下の3つの階層を使っています。

  • @layer base {} -- 最も優先順位が低い
    h1aなどの基本的なHTML要素のスタイルはここに配置する
  • @layer components {}
    コンポーネント固有のスタイルはここに配置する
  • @layer utilities {} -- 最も優先順位が高い
/* app/assets/tailwind/application.css */
@import "tailwindcss";

@theme {

}
@layer base {

}
@layer components {

}
@layer utilities {

}

@layerはTailwindではなくCSS自体の記法です。
Tailwindでは、生成されるCSSが「base」->「components」->「utilities」の順になるように構成されていて、結果としてその順で上書き関係が整理されます。

もちろん、各@layer{ }の中に書いたスタイルは、CSSの特性に従ってファイルの末尾(下)に近いものほど優先されます。
「同一レイヤ内では後勝ち」「レイヤが違う場合はレイヤ順」と覚えておくとよいでしょう。

そして、Tailwindのユーティリティクラスのビルド結果は、常に優先度が最も高い@layer utilities {}に配置されます(つまりTailwindのユーティリティクラスは、まさに@layer utilities {}に配置されていることにやっと自分も気づきました😅)。

/* app/assets/tailwind/application.css */
@import "tailwindcss";

@theme {
  --font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --color-primary-50: #f0f9ff;
}
@layer base {
  h1, h2, h3, h4 { ... };
}
@layer components {
  .nav { ... };
}
@layer utilities {
  /* HTMLに書いたユーティリティクラスは全部ここに出力されるのと同じ */
}

階層化がこのように設計されていることで、baseに書いたものは常にcomponentsutilitiesで上書き可能になり、componentsに書いたものは常にutilitiesで上書き可能になります。

これによって、

  • !importantを使わずに済むようになる
    (ただしTailwindにも!をサフィックスする記法が一応ありますが使ったことはありません)

  • 同一要素のclass=" "内の並び順は基本的に結果に影響しなくなる(同じ詳細度のルール同士なら生成CSS側の順序で決まる)
    一方、親要素の指定が子に効く設計(例: text-slate-*などの色やfont-*の継承)は当然あります。

つまり、「class=" "に書いたものが確実に効く」世界を目指しているということです。

Tailwindはユーティリティクラス中心なので、CSSのidをJavaScriptのフックとして使いやすいのもありがたい点です。

逆に、h2 li aセレクタのように文書構造に依存する従来の書き方は、Tailwindの考え方にはそぐわないものです。Tailwindでは、効かせたい場所にclass=" "でユーティリティクラスを書くという考え方です。

🔗 参考: style属性に書いても効かないCSSがある

HTMLのstyle=""属性によるインラインスタイルは強力ですが、疑似要素や疑似クラスなどを直接書けないという制約があります。また、レスポンシブやダークモードのような「条件付き」の記述もHTMLで扱いにくくなります。

すべてではありませんが、Tailwindのユーティリティクラスでは、style属性では書けない疑似クラスやメディアクエリなども書けます。

<!-- 擬似クラスの例 -->
<button class="hover:bg-red-500 focus:ring-2">
<!-- メディアクエリの例 -->
<div class="sm:hidden md:block">
<!-- ダークモードの例 -->
<div class="dark:text-white">
<!-- アニメーションの例 -->
<div class="transition hover:scale-105 duration-200">

Tailwindでも書けないような複雑なスタイルももちろんありますが、それこそ@layer base {}@layer components {}に通常のCSSとして書けばよいということです。

詳しくは今後の記事に譲りますが、Tailwindではユーティリティクラスばかり使うのではなく、@themeや3つの@layerもうまく使い分けることが大事だとわかってきました。


まとめ: Tailwindは、CSSのカスケードや詳細度のトラブルを減らして、コンポーネントの表示上の境界を定めるのに役立ちます。


🔗 Tailwindとコンポーネントの相性

ここまで書いてみて、ReactなどのコンポーネントでTailwindを採用しているところが目立つ理由が、これで腑に落ちました。

RailsのViewComponentでも、Tailwindのスタイルをコンポーネントに閉じ込めることで、コンポーネント内で確実にスタイルが効くようになり、baseやcomponentsのスタイルを確実に上書きできるのは、コンポーネントの独立性を固めるうえでたしかに嬉しいポイントだと思います。

参考: ViewComponent

viewcomponent/view_component - GitHub

Lookbookでコンポーネントをプレビューするときも、周囲の親要素から思わぬ影響を受けるリスクの少ないTailwindはありがたいものだと思います。特にWebデザイナーとコラボするときのメリットは大きいでしょう。

参考: Lookbook Demo

lookbook-hq/lookbook - GitHub

その代わりと言っていいのかどうかわかりませんが、Bootstrapなどのような既成の無料スタイルをダウンロードして使ったり、有料のスタイルを購入するというのはTailwindだと少々やりにくそうに思えました。

参考: Bootstrap · 世界で最も利用されているHTML、CSS、JSのライブラリです。

個人の感想ですが、Tailwindは既製品のスタイルを適当に当てはめて作るよりも、Webデザイナーと共同で作り込んでいくのに向いていそうです。Webデザイナーにも感想を聞いてみたいところです。何となくですが、Tailwindの発想はデザイナー寄りな気がしています。

以下の記事で「Tailwindはデザインシステムを確立しておかないと失敗しやすい」と指摘していますが、実はよく考えれば、TailwindでなくたってWebシステムはデザインシステムを確立しておく方がよいはずです。Tailwindは、デザインシステムの必要性に直面させる効果があるとも言えそうです。

Tailwind CSSをカオスにしないための5つのベストプラクティス(翻訳)

フロントエンド開発者がデザインに向き合うための厳選ベストプラクティス7選(翻訳)

もちろんBootstrapでもScssでもPostCSSでも、既存のCSSシステムに慣れていて知見が積まれているのであれば、全然その方がよいと思います(チームの同意がなければTailwindを導入しようもありませんし)。

🔗 最後に: Tailwind v4のbreaking changes

以下の記事にも書きましたが、Tailwindはv4で大きな変更がありました。

Tailwindcss 4.0の変更点がtailwindcss-railsに与える影響

  • JavaScriptへの依存を減らし、高速化した
  • tailwind.config.jsをデフォルトで使わなくなり、基本的に素のapplication.cssでできるだけ普通のCSSの機能を使うようになった
  • ユーティリティクラスの一部がリネームされた

変更の主な目的は「ゼロコンフィグを目指す」ことにあるようです。

しかしv3からv4の変化は結構大きいので、v3に乗っていた人たちは大変だったと思います。

素のapplication.cssになったために、いわゆるGlob記法(../*)で複数ファイルをパス指定できなくなりましたし、プラグインもnodeなどで外から導入するしかなくなったようです(個人的にはTailwindにプラグインが必要とはあまり思えないのですが)。

ともあれ、今後はこのような大きな変更はないと思いたいです🙏🙏
そういえばViewComponentも、v4で長期サポートに入り、機能はひととおり完成したと宣言しています↓。

As of version 4, ViewComponent is in Long-Term Support and considered feature-complete.
ViewComponentより

自分の中では、Railsでコンポーネントするための機がやっと熟したということにしています。
コンポーネントがどうか盛り上がりますように⛩️
YouTube動画も貼っておきます↓

関連記事

tailwindcss-rails README(翻訳)

Tailwind CSS: 開発者が知っておきたいデリケートなUIデザインの便利技11種(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。