Tailwind CSSは使うべきか?——Alcogyがscssを選ぶ理由

近年のフロントエンド開発において「Tailwind CSSは使うべきか?」という疑問が多く上がっています。utility-firstによる開発速度の恩恵は理解しつつも、AlcogyのSvelteKitプロダクトでは採用を見送っています。なぜ採用していないのか、逆にどんな現場ならTailwindが向いているのかをご紹介します。

Tailwind CSSの紹介

Tailwind CSSは、flexgap-4text-gray-600のような小さなユーティリティクラスをHTML(Svelteならテンプレート)に直接並べてスタイリングするutility-firstのCSSフレームワークです。

<div class="flex items-center gap-3 px-3 py-5 bg-gray-100 rounded-sm">...</div>

一番の強みはHTMLとCSSファイルを行き来しなくていい開発速度です。クラス名を考えたり、別ファイルにセレクタを書きに行ったりする必要がなく、マークアップを書きながらその場でスタイルを完結させられます。spacingやカラーパレットといったデザイントークンも標準の設定にあらかじめ用意されているため、チーム独自のCSS設計ルールを整備しなくても、ある程度統一感のあるUIが自然にできあがります。

JITエンジンによって、実際に使われているクラスだけを検出してCSSを生成する仕組みも持っており、巨大なユーティリティクラス群を持ちながらも本番のバンドルサイズは小さく保てます。加えて、shadcn/uiやTailwind UIのようなコンポーネント集の多くがTailwindを前提にしていること、AIによるコード生成でもTailwindパターンの学習データが豊富なことから、UIをすばやく組み立てたい場面では非常に強力な選択肢です。

Alcogyでの採用について

AlcogyではTailwind CSSを使っていません。コンポーネント側は<style lang="scss"></style>でスコープを切り、色やサイズといった全体で統一すべき値はapp.scssのグローバルなCSS変数として定義しています。理由は次の4点です。

1. HTMLの見通しが悪くなる

通常のクラス定義であれば、テンプレート側は意味のある名前だけが残ります。

<div class="wrap">...</div>

Tailwindの場合、スタイルの数だけクラスが並びます。

<div class="flex justify-content align-center gap-3 px-3 py-5 bg-gray-100 rounded-sm">...</div>

1つのdivなら大きな問題ではありませんが、ネストが深いコンポーネントや条件分岐でクラスが増えていくコンポーネントでは、テンプレートの構造そのものよりもクラス文字列を目で追う時間のほうが長くなりがちです。

2. 思わぬバグが発生する

Tailwindは、ソースコードをテキストとしてスキャンし、その中に完全な形で登場するクラス名だけを検出してCSSを生成します。そのため、次のように文字列をテンプレートリテラルで組み立ててしまうと、正しく適用されません。

<script>
  let $active = true;
  const $color = $active ? 'blue' : 'gray';
</script>

<div class="bg-{$color}-300">...</div>

bg-blue-300bg-gray-300という完全なクラス名がソースコード上のどこにも文字列として存在しないため、JITのスキャナーはこれらのクラスを検出できず、対応するCSSを生成しません。実行時にはclass属性に正しい文字列が入っているように見えるので、開発中に気づきにくく、本番ビルドで初めてスタイルが当たっていないことに気づく、というのが典型的なハマり方です。動的な値をクラス名の一部に埋め込みたい場合は、あらかじめ完全なクラス名の組み合わせをsafelistに登録するなどの回避策が必要になり、ロジックとスタイル定義が分離しにくくなります。

3. 階層定義が難しい

通常のCSS(scss)であれば、祖先要素の状態に応じた子孫要素のスタイルを素直に書けます。

table tr.disabled td {
  color: var(--color-text-muted);
}

Tailwindのutility-firstな発想では、こうした「祖先の状態に応じて子孫を変える」スタイルはあまり得意ではありません。任意バリアント([&_tr.disabled_td]:opacity-50のような記法)で無理やり表現することはできますが、可読性は大きく下がりますし、条件が増えるほどクラス文字列も複雑になります。複雑なカスケードを扱うテーブルやフォームのようなUIでは、素直にセレクタを書けるscssのほうが見通しが良いと感じています。

4. 設定がややこしい

デザインカンプに合わせて既存のスケールから外れた微調整をしたい場合、tailwind.config側でテーマを拡張するか、任意の値(w-[123px]のような記法)を都度指定することになります。細かい調整が積み重なるとconfig側の管理コストが上がり、purge対象のcontentパスの設定漏れで本番だけスタイルが消える、といった事故にもつながりやすい部分です。Alcogyのように会社としてブランドカラーやタイポグラフィをCSS変数として厳密に統一したいケースでは、app.scss:rootに変数を定義し、各コンポーネントがそれを参照するほうが、ルールとしてシンプルに保てます。

Tailwind CSSが向いているケース

以上を踏まえても、Tailwind CSSが向いている現場は明確にあると考えています。

  • 立ち上げ初期のプロトタイピングやPoC: デザイナー不在でも、標準のデザイントークンだけである程度統一感のあるUIをすばやく組めます。検証速度を優先するフェーズとは相性が良い選択です。
  • shadcn/uiのようなコンポーネントライブラリを前提にしたプロジェクト: utility classの生々しさはコンポーネント内部に閉じ込められ、利用側は素直なコンポーネントAPIだけを触ればよくなります。
  • AIによるコード生成を多用する開発フロー: 学習データにTailwindパターンが大量にあるため、UIコード生成の精度・速度が上がりやすい領域です。
  • チームの人数が多く、CSS設計のガバナンスを毎回徹底するコストをかけたくない場合: utility-firstは個々人のCSS設計スキルへの依存度を下げ、スタイルの書き方のばらつきを抑えられます。

Alcogyが受託開発やOEM提供のような長期保守を前提としたプロダクトでは、動的クラス生成のバグや複雑な階層セレクタを扱いにくいことが、保守フェーズで効いてくるコストだと捉えてscssを選んでいます。一方で、短期間で検証まで持っていきたいプロトタイピングやPoCの現場では、Tailwindの開発速度がそのまま強みになる場面も多く、プロジェクトの性質に応じて選び分けるべきものだと考えています。