はじめに
FBC Press: Tailwind CSS ― 仕組みと思想から理解する
本書は、プログラミングスクール FjordBootCamp(フィヨルドブートキャンプ) の教材として作成された、Tailwind CSS の教科書です。
本書は、Tailwind CSS のクラス一覧ではありません。
目的は、Tailwind CSS を「便利な短縮記法」として覚えることではなく、なぜ Tailwind CSS のような道具が生まれ、どんな問題を解決し、どこに限界があるのかを理解することです。
Tailwind CSS は、CSS を不要にする道具ではありません。CSS が大規模な開発で抱えてきた、グローバルな影響範囲、詳細度、命名、重複、チーム内のばらつきといった問題に対する、ひとつの設計上の答えです。その答えが妥当かどうかは、プロジェクトやチームによって変わります。本書では、利点だけでなく批判や向かない場面も扱います。
想定読者
本書は、次のような読者を想定しています。
- HTML/CSS の基礎を学んだことがある
- Flexbox と Grid の基本的な考え方を知っている
- Rails チュートリアル程度の開発経験がある
- React の基礎にも触れたことがある、またはこれから触れる予定がある
Tailwind CSS を初めて使う人でも読めるように書いていますが、完全な HTML/CSS 入門ではありません。CSS の基礎が曖昧な場合は、分からない用語を調べながら読み進めてください。
読み方
最初から順に読むなら、第1部から始めてください。第1部では、まだ Tailwind CSS のクラスをほとんど書きません。まず「なぜこの道具が必要になったのか」を理解します。
すぐに手を動かしたい場合は、付録Fから始めても構いません。Play CDN / Rails / Vite の 3 経路で、最小の動く例を作れます。そのあとで第3部に戻ると、導入手順の意味が理解しやすくなります。
すでに Tailwind CSS を使っている人は、第2部で仕組みを確認し、第6部と第7部で設計・運用の判断材料を拾う読み方もできます。
バージョン基準
本書は Tailwind CSS v4 系を前提にしています。2026-06 時点の公式ドキュメントを基準にしています。
ビルド速度、既定値、ディレクティブ、導入手順は将来のバージョンで変わる可能性があります。各章末の参考資料では、できるだけ公式ドキュメントや公式ブログなどの一次情報を示しています。実務で使うときは、必ず最新の公式情報も確認してください。
それでは、まず 第1部 で、Tailwind CSS が生まれる前の CSS の歴史と課題から見ていきます。
本書のバージョン基準: 本書は Tailwind CSS v4 系(執筆・確認時点で公式ドキュメントは v4.3、v4.0 は 2025-01-22 リリース/最終確認 2026年6月)を前提としています。ビルド速度などの数値・既定のブレークポイント値・ディレクティブの仕様は、将来のバージョンで変わる可能性があります。最新情報は各章末の公式ドキュメントで確認してください。v3 以前にしか当てはまらない記述には「v3 までは〜」と明記しています。
第1部 Tailwind CSS を理解する
この部では、まだ Tailwind CSS のクラスを 1 つも書きません。代わりに、「なぜ Tailwind CSS のようなものが必要とされたのか」 を理解します。
道具は、それが生まれた背景を知ると驚くほど使いやすくなります。Tailwind CSS は「CSS を書かなくする道具」でも「楽をするための近道」でもありません。CSS が大規模な開発で長年抱えてきた、ある具体的な問題に対する 1 つの答えです。その問題が何だったのかを知らないまま使うと、Tailwind は「クラス名がやたら長いだけの不思議な流儀」に見えてしまいます。
そこでまず、CSS の歴史をたどり(第1章)、作者がどんな課題に直面して Tailwind を作ったのかを一次情報で確認し(第2章)、その根っこにある「Utility First」という考え方を腰を据えて理解します(第3章)。
第1章 CSS の歴史と Tailwind CSS
1.1 CSS は何を解決するために生まれたのか
CSS(Cascading Style Sheets)は 1996 年に登場しました。それ以前の Web ページは、見た目の指定を HTML の中に直接書いていました。たとえば文字を赤くするには、次のように <font> タグを使いました。
<font color="red" size="5">セール開催中</font>
この書き方には大きな問題がありました。同じ「赤い見出し」をページ全体で 100 か所使っていたら、色を変えるときに 100 か所すべてを書き換えなければならないのです。見た目(赤い・大きい)と構造(これは見出しである)が、同じ場所にべったり貼り付いていました。
CSS はこの問題を解決するために生まれました。CSS の理想はこうです。
- HTML は文書の構造だけを書く(これは見出し、これは段落、これはリスト)
- CSS は見た目だけを別ファイルに書く(見出しは赤く大きく)
<!-- HTML: 構造だけ -->
<h1 class="headline">セール開催中</h1>
/* CSS: 見た目だけ */
.headline {
color: red;
font-size: 2rem;
}
こうしておけば、色を変えたいときは CSS の 1 か所を直すだけで、ページ全体に反映されます。この「構造と見た目を分ける」という考え方を 関心の分離(Separation of Concerns) と呼びます。これは長らく Web 制作の「正しい作法」とされてきました。
1.2 「関心の分離」の理想と現実 — HTML と CSS は本当に分離できたのか
ところが、現場で大きなアプリケーションを作っていくと、理想どおりにはいかないことが分かってきます。
問題は、「構造と見た目を別ファイルに置けば、本当に分離できているのか?」という点です。次の HTML と CSS を見てください。
<div class="hero">
<h1 class="hero-title">ようこそ</h1>
</div>
.hero-title {
color: white;
font-size: 3rem;
font-weight: bold;
}
たしかにファイルは分かれています。しかし hero-title というクラス名は、HTML 側にも CSS 側にも書かれていて、両者は名前で固く結びついています。HTML を見れば「hero-title という CSS があるはずだ」と分かり、CSS を見れば「hero-title という HTML 要素があるはずだ」と分かる。つまり、ファイルは別でも、お互いがお互いを知っているのです。
これは本当に「分離」と呼べるのでしょうか。後ほど第3章で詳しく扱いますが、この「ファイルは分かれているのに依存し合っている」という違和感が、のちに Tailwind CSS が生まれる思想的な出発点になります。ここでは「関心の分離は理想として語られたが、現実には HTML と CSS は名前を介して強く結びついていた」という事実を覚えておいてください。
1.3 CSS が大規模化で壊れる理由
CSS は小さなサイトではうまく機能します。問題は、ページ数が増え、関わる人が増え、何年も運用されるような大規模なプロジェクトで起きます。CSS には、規模が大きくなると牙をむく 3 つの性質があります。
(1) グローバルスコープ
CSS のセレクタは、原則としてページ全体に効きます。.title { color: red; } と書けば、ページ上のすべての .title が赤くなります。これは便利な反面、危険です。誰かが別の場所で .title を定義すると、意図しない場所まで巻き込んでしまいます。JavaScript には関数スコープやモジュールがありますが、素の CSS には「この CSS はこのコンポーネントの中だけ」という仕組みがありませんでした。
(2) 詳細度(Specificity)
CSS には、複数のルールがぶつかったときにどちらが勝つかを決める「詳細度」というルールがあります。たとえば #main .title は .title より詳細度が高く、勝ちます。大規模なプロジェクトでは、この詳細度の競り合いが起きます。「なぜか色が変わらない」「!important を付けないと効かない」といった経験をした人は多いはずです。詳細度の戦いがエスカレートすると、CSS は誰にも制御できなくなっていきます。
(3) ソース順への依存
CSS の「C」はカスケード(cascade)——複数のルールがぶつかったとき、どれを適用するか決めるアルゴリズム——を意味します。カスケードは「origin(どこで定義されたか)→ 詳細度 → ソース順(後に書いた方が勝つ)」の順で勝者を決めます。問題は、この最後の「ソース順」です。詳細度が同じルールがぶつかると、CSS ファイルのどこに書いたかで勝敗が変わります。大規模なプロジェクトでは、ファイルの読み込み順や記述位置のわずかな違いで、意図せず別のスタイルが勝ってしまう——「順番に依存して壊れる」という予測しづらさが生まれます。
補足: よく「カスケード」と混同されますが、親要素の値が子に伝わる継承(inheritance)は別の仕組みです。継承は値が宣言されていない場合の補完であり、ここで言う「壊れやすさ」の原因はカスケード(とりわけソース順への依存)の方です。
この 3 つが組み合わさると、大規模な CSS では「この CSS を消したら、どこが壊れるか分からない」という恐ろしい状態が生まれます。怖くて消せないので、古いスタイルが残り続け、CSS ファイルはひたすら肥大化していきます。CSS は、書いた量に比例して、いえ、それ以上に管理コストが膨らんでいくのです。
1.4 命名規則による戦い — OOCSS / SMACSS / BEM の登場と限界
この「壊れやすさ」に立ち向かうため、開発者たちは命名規則(ネーミングのルール)で秩序を作ろうとしました。代表的なものが OOCSS・SMACSS・BEM です。
- OOCSS(Object Oriented CSS, 2009 年ごろ): 再利用できる「オブジェクト」として CSS をとらえ、構造と見た目(スキン)を分けて考える。
- SMACSS(2011 年ごろ): CSS を Base / Layout / Module / State / Theme の 5 種類に分類して整理する。
- BEM(Block Element Modifier): 最も広く普及した命名規則。
BEM では、クラス名を ブロック__要素--修飾子 という形で構造的に付けます。
<div class="card">
<h2 class="card__title">タイトル</h2>
<button class="card__button card__button--primary">送信</button>
</div>
card がブロック(独立した部品)、card__title がその中の要素、card__button--primary が修飾子(バリエーション)です。クラス名を見ただけで構造が分かり、しかもクラス名が長く具体的なので詳細度の戦いやグローバルスコープの事故が起きにくくなります。BEM は、CSS の壊れやすさに対する優れた処方箋でした。
しかし、BEM にも限界がありました。
- 命名がとにかく大変: 部品を作るたびに「これは何という名前のブロックか」「この要素は何と呼ぶか」を考え続けなければなりません。
dashboard-card__action-label--activeのような長く具体的な名前を、部品の数だけ考え続けることになります。プログラミングで「最も難しいのは命名だ」とよく言われますが、BEM はその命名を CSS の隅々まで強制します。 - CSS は結局増え続ける: 新しい部品を作るたびに、新しいクラスと新しい CSS ルールが増えます。
card__titleとarticle__titleがほとんど同じスタイルでも、別の名前なので別々に書くことになりがちです。 - HTML と CSS の往復が続く: 見た目を少し変えたいとき、HTML でクラス名を確認し、CSS ファイルを開き、該当箇所を探して直す、という往復が必要です。
つまり BEM は「壊れにくさ」を手に入れた代わりに、「命名コスト」と「CSS が増え続ける問題」は解決できませんでした。
1.5 CSS-in-JS とコンポーネント志向の波
2013 年に React が登場し、Web 開発はコンポーネント志向へと大きく舵を切りました。UI を「ボタン」「カード」「ヘッダー」といった部品(コンポーネント)の組み合わせとしてとらえ、それぞれを独立した単位として扱う考え方です。
コンポーネントは、構造(HTML/JSX)・振る舞い(JavaScript)・見た目(CSS)を 1 つの部品としてまとめたいという欲求を生みました。そこで登場したのが CSS-in-JS です。styled-components(2016 年)や Emotion などが代表例で、JavaScript のファイルの中に CSS を書きます。
const Button = styled.button`
color: white;
background: blue;
padding: 8px 16px;
`;
CSS-in-JS は、CSS のグローバルスコープ問題を解決しました。各コンポーネントに自動でユニークなクラス名が割り振られるため、スタイルが他へ漏れません。「コンポーネントと一緒にスタイルが移動する」という再利用性も得られました。
一方で、CSS-in-JS にも代償がありました。実行時にスタイルを生成するライブラリでは、ページ表示のたびに JavaScript で CSS を組み立てるためパフォーマンスのコストがかかります。また、結局「コンポーネントごとに固有のスタイルを書く」点は BEM と同じで、デザインに一貫性をもたらす仕組み(余白や色を揃える仕掛け)は別途用意する必要がありました。
1.6 「CSS を書かない」という発想の系譜
ここまでの流れ(BEM も CSS-in-JS も「部品ごとに固有の CSS を書く」発想)とは、まったく別の方向から問題に挑む系譜がありました。Atomic CSS / Functional CSS と呼ばれる考え方です。
この発想はシンプルです。「1 つの CSS プロパティだけを担当する、小さなクラスをあらかじめ大量に用意しておき、それらを HTML 側で組み合わせて見た目を作る」というものです。たとえば次のように。
<div class="flex p-4 bg-white rounded shadow">...</div>
flex は display: flex だけ、p-4 は内側の余白だけ、bg-white は背景色だけ、というように、1 クラス 1 役割の小さな部品(これをユーティリティクラス、単一の役割だけを持つ小さなクラス、と呼びます)を組み合わせています。
この系譜には先行者がいました。
- Tachyons(2014 年ごろ): 「ブラウザの中でデザインするための、関数型 CSS ツールキット」を掲げ、小さな単機能クラスを組み合わせてデザインする手法を確立しました。Tachyons 自身が「これは単なる CSS フレームワークではなくデザインシステムだ」と述べている点は重要です。後の Tailwind の思想を先取りしています。
- Basscss・Atomic CSS(Yahoo! の ACSS) など、同時期に複数の試みがありました。
このアプローチは、当時「インラインスタイルと同じではないか」「HTML が汚い」と強い批判を浴びました(この批判は今も Tailwind に向けられ続けています。第28章で正面から扱います)。しかし同時に、「部品ごとに CSS を書かないので、CSS がほとんど増えない」「クラスを組み合わせるだけなので命名で悩まない」という、BEM や CSS-in-JS が解決できなかった問題への明確な答えでもありました。
1.7 この歴史の上に Tailwind CSS がどう位置づくか
ここまでの歴史を整理すると、CSS は次の課題と戦い続けてきたことが分かります。
| 時代 / 手法 | 解決したこと | 残った課題 |
|---|---|---|
| 素の CSS | 構造と見た目の分離 | グローバル・詳細度・カスケードで壊れる |
| BEM など命名規則 | 壊れにくさ | 命名コスト、CSS が増え続ける |
| CSS-in-JS | スコープ・コンポーネント化 | 一貫性は別途必要、実行時コスト |
| Atomic / Functional CSS | CSS が増えない、命名不要 | 「HTML が汚い」批判、開発体験が未成熟 |
Tailwind CSS は、この一番下の Atomic / Functional CSS の系譜に位置します。ただし Tailwind が画期的だったのは、思想を発明したことではありません。Tachyons などの先行者がすでに示していた「ユーティリティを組み合わせる」という考え方に、
- 制約のある統一されたデザインシステム(余白・色・サイズがあらかじめ整ったスケールになっている)
- 必要なクラスだけを自動生成する仕組み(HTML が汚い以前に、まず巨大な CSS にならない工夫。第4章で扱います)
- 優れた開発体験(エディタ補完、レスポンシブやホバーへの対応)
を組み合わせ、実務で本当に使える完成度にまで仕上げた点にあります。
次の第2章では、この Tailwind を作った人物 Adam Wathan が、まさにここで挙げた課題のどれに困り、何を考えて Tailwind を生み出したのかを、本人の言葉(一次情報)から追っていきます。
参考資料
- CSS(MDN Web Docs)
- BEM 公式 — Introduction
- Tachyons(Functional CSS Toolkit)
- Adam Wathan「CSS Utility Classes and "Separation of Concerns"」(2017)
- Tailwind CSS Docs — Styling with utility classes
第2章 Tailwind CSS 誕生の背景
第1章では、CSS が抱えてきた課題を歴史としてたどりました。この章では、その課題に対して 1 人の開発者がどう向き合い、Tailwind CSS が生まれたのかを、本人の発信という一次情報を中心に見ていきます。
2.1 Adam Wathan とは何者か
Tailwind CSS の作者は Adam Wathan(アダム・ウェイサン) です。彼はフルスタックの開発者であり、技術書や有料講座、ポッドキャストなどを通じて知識を発信してきた人物でもあります。
彼の経歴で重要なのは、Tailwind 以前から デザインと CSS の設計に強い関心を持っていたことです。彼はデザイナーの Steve Schoger とともに、デザインの実践書 『Refactoring UI』 を執筆しています。「きれいな UI をどう設計するか」を体系的に考えてきた人物が、その実装手段として CSS の書き方を突き詰めた結果が Tailwind だ、という背景を押さえておくと、Tailwind が単なる CSS フレームワークではなく「デザインシステムを作るための道具」である理由が腑に落ちます。
なお、Tailwind CSS は現在 Tailwind Labs という会社によって開発・運営されています。Adam Wathan を中心とした小さなチームが、OSS としての Tailwind CSS と、後述する有料製品の両方を手がけています。
2.2 きっかけとなったブログ記事「Separation of Concerns」再読
Tailwind の思想を理解するうえで最も重要な一次情報が、2017 年 8 月に Adam Wathan が公開したブログ記事 「CSS Utility Classes and "Separation of Concerns"」 です。これは Tailwind CSS が公に登場する直前に書かれた、いわば思想の宣言文です。
この記事で彼が投げかけたのは、第1章でも触れた問いです。「関心の分離は、本当に HTML と CSS を分けることなのか?」
彼はこう論じます。HTML と CSS が別ファイルにあっても、たとえば .hero-title のようなクラスでは、CSS が HTML の構造に依存しています。「ヒーローのタイトル」という HTML 側の都合に合わせて CSS のクラスを作っているからです。これは見方を変えれば、HTML と CSS が双方向に依存している状態です。
そこで彼は、依存の「向き」に注目します。選択肢は 2 つあると言います。
- CSS が HTML に依存する(
.hero-titleのように、HTML の構造に合わせて CSS を書く)→ HTML は変えやすいが、CSS は再利用しにくく、増え続ける。 - HTML が CSS に依存する(
text-white text-2xl font-boldのように、あらかじめ用意された汎用クラスを HTML 側で組み合わせる)→ CSS は完全に再利用可能で増えない。その代わり HTML はクラスでにぎやかになる。
彼の結論は、多くのプロジェクトでは後者、すなわち 「再利用可能な CSS」を選ぶ方が利益が大きい、というものでした。この「依存の向きを反転させる」という発想こそが、Utility First の核心です(第3章で深掘りします)。
2.3 自作 CSS から生まれた最初の Tailwind
重要なのは、Tailwind が「理論を先に立てて作られた」のではなく、実際のプロジェクトの中から生まれたという点です。
Adam Wathan は自分のプロジェクトで、繰り返し使う小さなユーティリティクラス(.p-4 のような)を自前で書きためていました。最初はちょっとした便利クラスの集まりでした。やがてそれを体系化し、設定で色や余白のスケールを生成できるようにしていったものが、フレームワークとしての Tailwind CSS になりました。最初のバージョンが公開されたのは 2017 年です。
この「実務での痛みから生まれた」という出自は、Tailwind が机上の理想論ではなく、現場で使える実用性を重視している理由を説明します。
2.4 「ユーティリティの自作」から「フレームワーク化」への転換点
個人の便利クラス集が、世界中で使われるフレームワークになるには、いくつかの転換が必要でした。
- 設定からの生成: 色・余白・フォントサイズなどを設定ファイルで定義すると、そこから整合性のとれたユーティリティクラス群が自動生成される仕組み。これにより「制約のあるデザインシステム」が手に入りました。
- レスポンシブやホバーへの対応:
md:やhover:といったプレフィックス(バリアント)で、メディアクエリや擬似クラスをユーティリティのまま扱えるようにしたこと。これがインラインスタイルとの決定的な違いになりました(第3章・第6章)。
これらにより、Tailwind は「ただの便利クラス集」から「デザインシステムを構築するための基盤」へと進化しました。
2.5 JIT エンジン以前の課題(巨大な生成 CSS と PurgeCSS)
ただし、初期の Tailwind には大きな弱点がありました。生成される CSS が巨大だったのです。
Tailwind は、ありとあらゆる色・余白・サイズの組み合わせをクラスとして用意します。しかも md: や hover: などのバリアントまで掛け合わせると、組み合わせは爆発的に増えます。初期の方式では、これらをあらかじめ全部生成していたため、開発時の CSS は数 MB に達することもありました。
そのままでは本番環境に出せないので、当時は PurgeCSS というツールを併用するのが定番でした。これは「実際に HTML で使われているクラスだけを残し、使っていないクラスを削除する」ツールです。これで本番の CSS は小さくなりますが、設定が必要で、「開発時は巨大、ビルド時に削る」という二段構えは、開発体験としても仕組みとしてもスマートとは言えませんでした。
2.6 v1 → v2 → v3(JIT 標準化)→ v4(新エンジン)の流れ
Tailwind はこの弱点を、エンジンの進化によって根本的に解決していきます。バージョンの流れは、そのまま「巨大な CSS 問題をどう克服したか」の歴史です。
- v1.0(2019 年): 最初のメジャーバージョン。フレームワークとしての土台が固まる。
- v2.0(2020 年 11 月): ダークモード対応などを追加。
- JIT エンジン(2021 年 3 月にプレビュー公開): ここが大きな転換点です。「あらかじめ全部生成して後で削る」のをやめ、実際にコードで使われているクラスだけを、その場で(Just-in-Time に)生成する方式に変えました。これにより PurgeCSS が不要になり、開発時から CSS が小さくなり、ビルドも劇的に速くなりました。さらに
top-[-113px]のような任意の値を角かっこで書けるようにもなりました。JIT は v2.1 で本体に統合され、v3.0(2021 年 12 月)で既定の動作になりました。 - v4.0(2025 年 1 月 22 日): エンジンを Rust で書き直した新エンジン(コードネーム Oxide)を搭載し、さらに高速化。設定方法も
tailwind.config.js中心から CSS ファースト(@themeを CSS に書く)へと大きく変わりました。本書はこの v4 を前提にしています(詳細は第29章)。
「巨大な CSS をどうするか」という初期の課題が、JIT という仕組みの発明によって解決され、それが Tailwind を実務で本当に使えるものにした——この流れは、第4章「Tailwind CSS はどう動くのか」の伏線になります。
2.7 ビジネスとしての Tailwind(OSS と収益の両立)
Tailwind を語るうえで見逃せないのが、OSS でありながら継続的に開発される仕組みを作った点です。
Tailwind CSS 本体は無料のオープンソースです。では Tailwind Labs はどうやって収益を得て、専任で開発を続けているのでしょうか。答えは、Tailwind を使った有料製品です。
- Tailwind UI(現在は Tailwind Plus に名称変更): Tailwind で作られた高品質な UI コンポーネント・テンプレート集。これを販売しています。
- Headless UI(スタイルを持たないアクセシブルな UI 部品)や Heroicons(アイコン集)など、本体を補完する OSS も提供しています。
「無料の OSS でユーザーを増やし、その上に乗る有料の完成品で収益を得る」というモデルにより、Tailwind は特定企業のスポンサー頼みにならず、自走できる開発体制を築きました。これは、多くの OSS が資金難で停滞するなかで、Tailwind が継続的に大きな進化を続けられている理由の 1 つです。
参考資料
- Adam Wathan 公式サイト
- Adam Wathan「CSS Utility Classes and "Separation of Concerns"」(2017)
- Tailwind CSS Blog(バージョン告知の一覧)
- 「Just-in-Time: The Next Generation of Tailwind CSS」(2021-03-15)
- 「Open-sourcing our progress on Tailwind CSS v4.0」(2024-03-06、新エンジン Oxide の出典)
- Tailwind CSS v4.0(2025-01-22)
- Tailwind CSS GitHub リポジトリ
第3章 Tailwind CSS の設計思想
第1章で歴史を、第2章で誕生の経緯を見てきました。この章では、Tailwind の根っこにある Utility First(ユーティリティファースト) という思想を、メリットだけでなく、それに向けられる批判への反論まで含めて、じっくり理解します。ここを腰を据えて理解しておくと、第4部以降で個々のクラスを学ぶときの「なぜこう書くのか」がすべてつながります。
3.1 Utility First とは何か
Utility First とは、単一の役割だけを持つ小さなクラス(ユーティリティクラス)を組み合わせて、デザインを組み立てるという考え方です。「まずユーティリティで作る」から Utility First です。
具体例を見ましょう。よくある「カード」の UI を、従来の方法と Tailwind で書き比べます。
従来の方法(BEM 風):
<div class="card">
<h2 class="card__title">通知</h2>
<p class="card__body">新しいメッセージがあります。</p>
</div>
.card {
max-width: 24rem;
padding: 1.5rem;
background-color: white;
border-radius: 0.5rem;
box-shadow: 0 1px 3px rgb(0 0 0 / 0.1);
}
.card__title {
font-size: 1.25rem;
font-weight: 700;
}
.card__body {
color: #4b5563;
}
Tailwind の方法:
<div class="max-w-sm p-6 bg-white rounded-lg shadow">
<h2 class="text-xl font-bold">通知</h2>
<p class="text-gray-600">新しいメッセージがあります。</p>
</div>
Tailwind 版では、CSS ファイルを 1 行も書いていません。max-w-sm(最大幅)、p-6(内側の余白)、bg-white(背景白)、rounded-lg(角丸)、shadow(影)といったユーティリティクラスを HTML 側で組み合わせるだけで、同じ見た目を作っています。
このとき生成される CSS は、おおよそ次のような単機能のルールの集まりです(実際の出力は簡略化しています)。
.p-6 { padding: 1.5rem; }
.bg-white { background-color: #fff; }
.rounded-lg { border-radius: 0.5rem; }
/* ... 使ったクラスの分だけ ... */
ポイントは、これらのクラスはこのカード専用ではないことです。p-6 も bg-white も、サイト中のどこでも何度でも使い回せます。だから、新しい部品を作っても CSS は増えません。
3.2 なぜインラインスタイルではダメなのか
ここで多くの人が抱く疑問があります。「クラスをたくさん並べるくらいなら、style 属性で直接書くインラインスタイルと同じでは?」というものです。
<!-- インラインスタイル -->
<div style="padding: 1.5rem; background-color: white; border-radius: 0.5rem;">...</div>
見た目だけなら、確かに似ています。しかし Tailwind のユーティリティには、インラインスタイルにはできないことが 3 つあります。これが決定的な差です。
(1) 制約(デザインの一貫性)
インラインスタイルでは padding: 13px でも padding: 17px でも何でも書けてしまいます。自由すぎて、気づけば余白がバラバラになります。一方 Tailwind の p-4・p-6・p-8 は、あらかじめ決められたスケール(4px 刻みなど)の上に乗っています。選べる値が制限されているからこそ、誰が書いても余白や色が揃い、デザインに一貫性が生まれます。これは「制約は不自由ではなく、品質を守る仕組み」という Tailwind の重要な思想です(3.3 で詳述)。
(2) 状態(ホバー・フォーカスなど)
インラインスタイルでは、:hover や :focus のような状態に応じたスタイルが書けません。マウスを乗せたら色を変える、といったことが style 属性では不可能です。Tailwind なら hover:bg-blue-700 のように、バリアントを付けるだけで状態に対応できます。
<button class="bg-blue-500 hover:bg-blue-700">送信</button>
(3) レスポンシブ(画面幅への対応)
インラインスタイルではメディアクエリ(画面幅に応じた切り替え)が書けません。Tailwind なら md: のようなバリアントで対応できます。
<div class="text-sm md:text-lg">画面が広いと大きい文字</div>
この (2)(3) ができることこそ、「Tailwind はインラインスタイルとは違う」と言える根拠です。公式ドキュメントも、ユーティリティクラスを使う理由として、まさにこの「制約・状態・レスポンシブ」を挙げています。
3.3 「制約のあるデザイン」がもたらす一貫性
3.2 で触れた「制約」を、もう少し深く見ます。これは Tailwind を理解するうえで最も大切な発想の 1 つです。
たとえば文字色を考えてみましょう。CSS では color に約 1,600 万色を指定できます。自由です。しかしチーム開発では、この自由がバラつきを生みます。ある人は #3b82f6、別の人は #3c83f5、また別の人は #3a80f0——ほとんど同じだけれど微妙に違う青が、サイト中に散らばっていきます。
Tailwind は、色を blue-500・blue-600 のような決められたパレットとして提供します。選択肢を絞ることで、「誰が書いても同じ青になる」状態を作ります。余白も --spacing を基準にした段階的な値(p-4・p-6・p-8…)として扱い、フォントサイズも sm/base/lg/xl…と段階が決まっています。
これは デザイントークン(デザイン上の決め事を、値の一覧として定義したもの)の考え方そのものです。Tailwind を使うということは、暗黙のうちに「制約のあるデザインシステムの上で作業する」ことを意味します。だからこそ、デザイナーがいなくても、ある程度整った見た目になりやすいのです。この制約は第5章「テーマシステム」で自分好みにカスタマイズできます。
3.4 関心の分離の再定義 — 分けるべきは HTML と CSS ではない
第2章で見た Adam Wathan の主張を、ここで改めて整理します。彼の主張はこうです。
本当に分けるべきなのは「HTML と CSS」ではなく、「再利用できないもの」と「再利用できるもの」だ。
従来の .card__title のようなクラスは、「カードのタイトル」という特定の文脈に縛られていて再利用できません。ファイルは分かれていても、HTML の都合に CSS が縛られています。
Tailwind のユーティリティは逆です。text-xl も font-bold も、特定の文脈に縛られておらず、どこでも再利用できます。HTML 側は「このユーティリティたちを使う」と宣言するだけ。依存の向きが「CSS → HTML」から「HTML → CSS」へ反転しています。
この反転によって、CSS は「特定のページのための、消すのが怖いコード」ではなく、「どこでも使える、安定した部品の集まり」になります。これが Tailwind の言う「関心の分離の再定義」です。
3.5 命名からの解放
第1章で、BEM の大きな負担は「命名」だと述べました。Tailwind は、この負担をほぼゼロにします。
p-6 bg-white rounded-lg shadow と書くとき、あなたは何も命名していません。「このカードを何と呼ぶか」「このタイトルのクラス名は何か」を考える必要がないのです。プログラミングで最も難しいことの 1 つが命名だとすれば、その難所を 1 つ丸ごと回避できるのは、地味ですが非常に大きな利点です。
もちろん、繰り返し使う部品は最終的にコンポーネント化します(第6部)。しかし「とりあえず作る」段階で命名を強制されないことは、開発のリズムを大きく軽くします。
3.6 CSS が線形に増えない理由
従来の方法では、ページや部品を追加するたびに CSS が増えました。新しいボタンを作れば新しいクラスと新しいルールが必要で、プロジェクトが大きくなるほど CSS ファイルは際限なく膨らみます。
Tailwind では、CSS の量は「使っているユーティリティの種類数」でほぼ頭打ちになります。p-4 を 1 か所で使おうと 1,000 か所で使おうと、生成される CSS は .p-4 { padding: 1rem; } の 1 つだけだからです。新しいページを 100 個追加しても、すでに使っているユーティリティの組み合わせで作る限り、CSS はほとんど増えません。
公式ドキュメントはこれを「CSS が線形に増えない(your CSS stops growing)」と表現しています。大規模で長期間運用されるプロジェクトほど、この性質は効いてきます。
3.7 「醜い HTML」問題への作者の回答
Tailwind に最もよく向けられる批判が、「クラスが大量に並んで HTML が読みにくい(醜い)」というものです。
<button class="inline-flex items-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
送信
</button>
確かに、初めて見ると面食らうでしょう。これに対する作者やコミュニティの回答は、おおむね次のとおりです。
- トレードオフである: HTML が「にぎやか」になる代わりに、CSS ファイルの肥大化・命名・詳細度の戦いといった、より深刻な問題から解放される。どちらの「醜さ」を引き受けるか、という選択である。
- 繰り返しはコンポーネント化で解決する: 同じボタンを何度も書くなら、Rails の部分テンプレートや React のコンポーネントとして 1 か所にまとめればよい。そうすれば、長いクラス列が見えるのは定義の 1 か所だけになる(第6部で詳しく扱います)。
- 見る場所が変わっただけ: 従来は「HTML を見て → CSS ファイルを開いて確認する」必要があった。Tailwind では、要素の見た目はその場の
classにすべて書いてある。ファイルを行き来せずに済むという利点の裏返しでもある。
「醜い HTML」は事実として認めつつ、それを上回る利益があり、かつコンポーネント化で実用上は緩和できる——これが Tailwind 側の立場です。この論争は重要なので、第28章「Tailwind CSS への批判と評価」で、批判側の主張も含めて改めて公平に扱います。
3.8 Utility First が向く場面・向かない場面の見取り図
最後に、思想を現実に当てはめるための見取り図を示します。Utility First は万能ではありません。
向いている場面:
- アプリケーションの UI(管理画面、ダッシュボード、SaaS など)。独自のデザインを、部品を組み合わせて素早く作りたいケース。
- コンポーネント志向のフレームワーク(React・Vue や、Rails の ViewComponent など)を使っていて、繰り返しをコンポーネントに畳み込めるケース。
- チームで開発し、デザインの一貫性を仕組みで担保したいケース。
向いていない・慎重になるべき場面:
- ごく小さな静的ページ 1 枚だけ。導入の手間が利益を上回ることがある。
- CMS が吐き出す本文 HTML のように、クラスを付けられない外部由来のマークアップを整えたいケース(ただしこれは
@tailwindcss/typographyのproseで対応できます。第11章)。 - コンポーネント化の仕組みがなく、同じ長いクラス列を手で何度もコピーするしかない環境。重複が負債になりやすい。
重要なのは、「向くか向かないか」はプロジェクトの性質とチームの体制で決まるということです。第31章「Tailwind CSS を選ぶべきか」で、この判断を具体的なチェックリストに落とし込みます。
ここまでで、Tailwind が「なぜそういう道具なのか」という土台が固まりました。次の第2部では、いよいよ Tailwind が内部でどう動いているのか——魔法に見える仕組みの正体に踏み込んでいきます。
参考資料
- Tailwind CSS Docs — Styling with utility classes
- Adam Wathan「CSS Utility Classes and "Separation of Concerns"」(2017)
- Tailwind CSS Docs — Theme(制約・デザイントークン)
- Tailwind CSS v4.0(思想と最新動向)
演習(第1部)
手を動かす前に、まず「なぜ」を自分の言葉にできるか確認しましょう。
- 説明してみよう: 「素の CSS が大規模化で壊れる 3 つの理由」を、人に説明するつもりで 3 行で書き出してください(ヒント: §1.3)。
- 考えてみよう: Adam Wathan の言う「依存の向きを反転させる」とは、
.hero-title方式と Tailwind 方式で、HTML と CSS のどちらがどちらに依存しているか、という話でした。あなたの言葉で図にしてみてください(§2.2・§3.4)。 - 意見を持とう: あなたが過去に関わった(または学習した)プロジェクトは、第3章の「Utility First が向く/向かない」のどちらでしたか。理由とともに 1 つ挙げてください。
第2部 Tailwind CSS の仕組み
第1部では「なぜ Tailwind CSS が必要とされたのか」を理解しました。この第2部では、視点を内側に向けます。Tailwind CSS は、内部でいったい何をしているのかを解き明かします。
Tailwind を使い始めると、不思議に感じることがいくつもあります。「grid-cols-15 なんてクラス、どこにも定義していないのに、なぜ効くのか」「あれだけ大量のクラスがあるのに、なぜ本番の CSS は小さいのか」「@theme に書いた値が、どうしてクラスにもなり CSS 変数にもなるのか」。
これらは魔法ではありません。すべてビルドの仕組みで説明できます。仕組みを理解すると、トラブル(クラスが効かない、など)に強くなり、第4部以降のユーティリティの学習も「暗記」ではなく「理解」になります。
- 第4章では、Tailwind がコードを読み取って CSS を生成する全体の流れを見ます。
- 第5章では、色や余白などのテーマ(デザイントークン)がどう定義され、どうクラスと CSS 変数になるのかを見ます。
- 第6章では、
hover:やmd:などのバリアントが、どうやってセレクタに変換されるのかを見ます。
第4章 Tailwind CSS はどう動くのか
4.1 全体像 — テンプレート走査 → 必要なクラスのみ CSS 生成
まず全体像をつかみましょう。Tailwind の動作は、驚くほどシンプルな 1 つの流れに集約できます。
- あなたが書いた HTML・ERB・JSX などのソースファイルを走査(スキャン)する
- その中に登場するクラス名らしき文字列を拾い集める
- 拾ったクラスに対応する CSS を、必要な分だけ生成する
ここで決定的に重要なのは、「使われているクラスだけを生成する」という点です。Tailwind には理論上、数十万通りのクラスが存在しえます(色 × 余白 × バリアントの組み合わせ)。しかし、それらをすべて出力するわけではありません。あなたのコードに p-6 が登場して初めて、.p-6 { padding: calc(var(--spacing) * 6); } という CSS が生成されます(この calc(...) という形の意味は §4.9・第5章で扱います。ここでは「使ったものだけ生成される」ことに注目してください)。登場しないクラスの CSS は、最初から作られません。
この「使うものだけ作る」という発想が、第1部で触れた「CSS が線形に増えない」性質と、本番ビルドが小さい理由を支えています。
4.2 JIT(Just-In-Time)の考え方
この「使われているクラスだけを、その場で生成する」方式を JIT(Just-In-Time、必要なときに) と呼びます。
第2章で歴史を見たとおり、初期の Tailwind は逆の発想でした。あらかじめ考えられる全クラスを生成しておき、本番ビルド時に PurgeCSS で使っていないものを削る、という二段構えです。これには、開発時の CSS が数 MB に膨らむ、ビルドが遅い、削除設定を間違えると本番でスタイルが消える、といった問題がありました。
JIT はこれを反転させました。「全部作って削る」のではなく、「使うものだけ最初から作る」。2021 年にプレビュー公開され、v3.0 で既定の動作になったこの仕組みにより、
- 開発時から CSS が小さい
- ビルドが速い
- 開発環境と本番環境で生成される CSS が完全に一致する(PurgeCSS による「削り忘れ・削りすぎ」が起きない)
top-[-113px]のような任意の値も、その場で生成できる
という利点が得られました。現在の Tailwind(v4)は、この JIT の考え方を前提に動いています。
4.3 v4 の新エンジン(Oxide / Lightning CSS)と高速化
v4 では、この JIT を実行するエンジンそのものが刷新されました。コードネーム Oxide と呼ばれる新エンジンです(出典は第29章および本章の参考資料)。
ポイントは 2 つです。
- パフォーマンスを重視した実装: 性能が重要な部分は Rust で実装されています。公式(v4.0 時点)の計測では、フルビルドが約 3.8 倍、増分ビルド(差分だけの再ビルド)が約 8 倍以上速くなり、変更がない場合の再ビルドはマイクロ秒単位で完了するとされています。
- Lightning CSS の採用: CSS のパース(解析)、ベンダープレフィックスの付与、
@importの解決、圧縮(minify)などを、Rust 製の高速ツール Lightning CSS がまとめて担当します。これにより、以前は別途必要だったpostcss-importやautoprefixerといったツールが、Tailwind 単体に取り込まれました。
開発者にとっての実感は「とにかく速い」ことと「周辺ツールの設定が減った」ことです。v4 で導入が簡単になった背景には、このエンジンの刷新があります。
4.4 自動コンテンツ検出(v4)と @source / 旧 content 配列の違い
4.1 で「ソースファイルを走査する」と述べました。では Tailwind は、どのファイルを走査すればよいと知るのでしょうか。ここは v3 と v4 で大きく変わった点です。
v3 までは、設定ファイルに「走査対象」を手で書く必要がありました。
// tailwind.config.js(v3 の書き方)
module.exports = {
content: [
'./app/views/**/*.html.erb',
'./app/javascript/**/*.js',
],
};
この content 配列の指定を忘れたり、パスを間違えたりすると、「クラスが効かない」という典型的なトラブルが起きました。
v4 では、この指定が原則不要になりました(自動コンテンツ検出)。Tailwind はプロジェクト内のファイルを自動的に走査対象とします。このとき、
.gitignoreに書かれたファイルは除外するnode_modules・バイナリファイル・CSS ファイル・ロックファイルは除外する
といった賢い既定値を持っています。つまり「ビルド対象になりそうなテキストファイル」を自動で見つけてくれます。
それでも明示的に指定したい場合(自動検出から外れた場所のテンプレートや、外部パッケージ内のクラスを拾いたい場合)には、CSS 側で @source ディレクティブを使います。
@import "tailwindcss";
/* 自動検出に加えて、このパスも走査対象にする */
@source "../node_modules/my-ui-library";
逆に特定パスを除外したいときは @source not "..."、自動検出を完全に止めたいときは source(none) を使います。v3 の content 配列が「CSS 側の @source と自動検出」に置き換わった、と理解しておけば十分です。
4.5 @import "tailwindcss" が展開するもの
v4 で Tailwind を読み込む CSS は、たった 1 行です。
@import "tailwindcss";
この 1 行が、内部的にいくつかの部品を読み込んでいます。中身を分解すると、おおよそ次の 4 つのレイヤーに対応しています。
- theme: テーマ変数(色・余白などのデザイントークン)の定義。
:rootに CSS 変数として並びます(第5章)。 - base: ブラウザ間の差異をならす土台のスタイル(Preflight と呼ばれるリセット CSS)。たとえば見出しの余白を消す、などの初期化です。
- components: コンポーネント向けのレイヤー(自分で書くクラスの置き場所として用意されている)。
- utilities:
p-6やtext-centerなどのユーティリティクラス本体。
この順番には意味があります。後で説明する CSS Cascade Layers(@layer)の仕組みにより、utilities は base より優先されるように設計されています。だから、Preflight がデフォルトで付けたスタイルを、ユーティリティで上書きできるのです。
必要に応じて、この 1 行を分割して特定のレイヤーだけ読み込むこともできますが、通常は @import "tailwindcss"; の 1 行で問題ありません。
4.6 CSS Cascade Layers(@layer)と詳細度の制御
第1章で、CSS の大きな悩みの 1 つは「詳細度の戦い」だと述べました。.btn と #main .btn がぶつかると、詳細度の高い後者が勝つ——この予測しづらさです。
v4 の Tailwind は、これを CSS Cascade Layers(カスケードレイヤー) という比較的新しい CSS の標準機能で制御しています。これは @layer を使って「レイヤーの優先順位」をあらかじめ宣言できる仕組みです。
/* 概念図: レイヤーの順番を先に決める */
@layer theme, base, components, utilities;
このように宣言すると、後ろのレイヤーが前のレイヤーに勝ちます。しかも重要なのは、レイヤー間の優先順位は詳細度より強いという点です。つまり、utilities レイヤーにあるユーティリティは、詳細度が低くても、base レイヤーのスタイルに確実に勝てます。
ただし、これが保証されるのは Tailwind が生成するレイヤー(theme / base / components / utilities)の内側での話です。あなたがレイヤーの外に書いた素の CSS や、!important を付けたスタイルは、この優先順位の枠組みの外で評価されるため、ユーティリティに勝つことがあります。「Tailwind のレイヤー内では詳細度の戦いから解放される」と理解してください。
これによって、Tailwind は「ユーティリティは常に意図どおり効く」状態を、詳細度の小細工なしに実現しています。第1章で見た「!important を付けないと効かない」といった戦いから解放されるのは、この仕組みのおかげです(なお、どうしても強制したいときのための ! important 記法は別に用意されています。4.8 参照)。
4.7 任意の値(arbitrary value)が動的に解決される仕組み
「top-[-113px] や grid-cols-15 のような、定義した覚えのない値がなぜ効くのか」。これは多くの人が最初に驚くところです。
Tailwind は、クラス名をパターンとして解釈します。たとえば p-{数値} というパターンを理解していて、p-6 を見れば padding の値をスケールから計算し、p-[13px] のように角かっこで任意の値が書かれていれば、その値をそのまま使った CSS を生成します。
<div class="top-[-113px] grid grid-cols-[1fr_500px_2fr]">...</div>
/* 生成される CSS(簡略化) */
.top-\[-113px\] { top: -113px; }
.grid-cols-\[1fr_500px_2fr\] {
grid-template-columns: 1fr 500px 2fr;
}
v4 では、この「動的な解決」がさらに広がりました。たとえば grid-cols-15 のように、スケールに明示的に用意されていない値でも、規則的に生成できるものは設定なしで使えます。
ただし、この仕組みには重要な前提があります。Tailwind は 4.1 で見たとおり、ソースをただのテキストとして走査して、登場した文字列を拾っているだけです。つまり、コード上に完全なクラス名の文字列として存在していなければ、生成されません。たとえば text-${color} のようにクラス名を動的に組み立てると、Tailwind はそれを 1 つの文字列として認識できず、CSS が生成されません。この「動的なクラス名の落とし穴」は、実務で頻発する代表的なつまずきなので、第27章のアンチパターンで詳しく扱います。
4.8 スタイル衝突の解決(後勝ち・! important・prefix)
ユーティリティを並べていると、相反するクラスが同時に付くことがあります。たとえば p-4 と p-8 を両方書いたら、どちらが勝つのでしょうか。
(1) 後勝ち(ソース順)
ここで注意したいのは、HTML の class 属性に書いた順番では決まらないことです。勝敗を決めるのは、生成された CSS の中での順番です。同じプロパティを扱うユーティリティ同士では、CSS で後に来るルールが勝ちます。
<!-- class の順番を入れ替えても結果は同じ。CSS 上の順序で決まる -->
<div class="p-8 p-4">...</div>
そのため「同じ要素に競合するユーティリティを 2 つ書く」のは避け、条件によってどちらか一方だけが付くように組むのが基本です(このための道具が第23章の tailwind-merge です)。
(2) ! important 修飾子
どうしても優先したいときは、クラスの末尾に ! を付けると !important が付与されます。
<div class="bg-red-500!">必ず赤</div>
ただし !important の多用は、第1章で見た「詳細度の戦い」を再び招きます。最終手段と考えてください。
(3) prefix(プレフィックス)
既存の CSS やサードパーティのスタイルとクラス名が衝突する環境では、すべてのユーティリティに接頭辞を付けて名前空間を分けられます。たとえば接頭辞を tw にすると tw:flex のように書きます。既存システムへ段階的に Tailwind を導入するときに役立ちます(第8章・第26章)。
4.9 生成 CSS を実際に覗いてみる
仕組みを腹落ちさせる一番の方法は、実際に生成された CSS を見ることです。手元で確認するなら、最小の入力 CSS を用意してビルドします。
入力(input.css):
@import "tailwindcss";
テンプレート(index.html)に p-6 と text-center だけを使ったとします。すると、出力 CSS には Preflight(base)とテーマ変数(theme)に加えて、おおよそ次のようなユーティリティだけが含まれます(実際の出力は簡略化しています)。
.p-6 { padding: calc(var(--spacing) * 6); }
.text-center { text-align: center; }
ここで p-6 が 1.5rem という固定値ではなく calc(var(--spacing) * 6) になっている点に注目してください。これは v4 が、余白をテーマ変数 --spacing を基準に動的計算していることを示しています。この「テーマ変数」こそが次の第5章の主役です。
ヒント: 細かいセットアップなしに生成結果を試したいときは、公式の Tailwind Play(https://play.tailwindcss.com/)が便利です。左に HTML、右に結果が出て、生成 CSS も確認できます。
参考資料
- Tailwind CSS Docs — Styling with utility classes(衝突解決・important・prefix)
- Tailwind CSS Docs — Detecting classes in source files(自動検出・@source)
- Tailwind CSS Docs — Functions and directives(@import など)
- Tailwind CSS v4.0(新エンジン・Lightning CSS)
- 「Open-sourcing our progress on Tailwind CSS v4.0」(新エンジン Oxide の出典, 2024-03-06)
- Tailwind Play(生成結果の確認)
第5章 テーマシステム
第4章の最後で、p-6 が calc(var(--spacing) * 6) という CSS 変数を使った値になることを見ました。この章では、その --spacing をはじめとするテーマ(デザイントークン)の仕組みを掘り下げます。v4 で最も大きく変わった部分であり、Tailwind を「自分のプロジェクトの色や余白に合わせて使う」ための心臓部です。
5.1 テーマとは何か(デザイントークンの一元管理)
第3章で、Tailwind の強みは「制約のあるデザイン」だと述べました。blue-500 や p-4 のように、選べる値があらかじめ決まっているからこそ、見た目が揃う、という話です。
この「選べる値の一覧」を定義しているのがテーマです。色・余白・フォントサイズ・ブレークポイント・角丸・影など、デザイン上の決め事をすべて値の一覧として持っています。こうした「デザイン上の決め事を値として定義したもの」を デザイントークンと呼びます。
テーマを一元管理することの意味は大きいものです。「ブランドカラーを少し変えたい」と思ったとき、テーマの定義を 1 か所直せば、それを使っているすべてのユーティリティに反映されます。デザインシステムの「単一の真実(Single Source of Truth)」になるわけです。
5.2 @theme ディレクティブの基本(v4)
v4 では、テーマを CSS ファイルの中に @theme ディレクティブで書きます。これが「CSS ファースト設定」と呼ばれる、v4 の目玉の変更です。
@import "tailwindcss";
@theme {
--color-mint-500: oklch(0.72 0.11 178);
--font-display: "Satoshi", sans-serif;
--breakpoint-3xl: 120rem;
}
たったこれだけで、bg-mint-500・text-mint-500、font-display、3xl: といった新しいユーティリティやバリアントが使えるようになります。
この変更の意味を、第1部の流れで押さえておきましょう。
v3 までは、設定を JavaScript ファイル(tailwind.config.js)に書いていました。
// tailwind.config.js(v3 の書き方)
module.exports = {
theme: {
extend: {
colors: { mint: { 500: '#19c39c' } },
},
},
};
CSS のためのデザイントークンを、わざわざ JavaScript で定義していたわけです。v4 はこれを「CSS のことは CSS で書く」という自然な形に戻しました(5.6 で移行を扱います)。
5.3 テーマ変数が「CSS 変数 + ユーティリティ生成」を兼ねる仕組み
ここが v4 のテーマシステムで最も重要な発想です。@theme に書いた変数は、2 つの役割を同時に果たします。
- 本物の CSS 変数になる:
@themeの中身は、出力 CSS の:rootにそのまま CSS カスタムプロパティとして並びます。だから、Tailwind を通さず素の CSS や JavaScript からも参照できます。 - ユーティリティクラスを生成する指示にもなる: 同時に Tailwind に対して「この変数に対応するユーティリティを作れ」と指示します。
公式ドキュメントの言葉を借りれば、「テーマ変数は単なる CSS 変数ではなく、新しいユーティリティクラスを作るよう Tailwind に指示するものでもある」のです。
@theme {
--color-mint-500: oklch(0.72 0.11 178);
}
この 1 行から、次の両方が生まれます。
/* 1. CSS 変数として :root に出力される */
:root {
--color-mint-500: oklch(0.72 0.11 178);
}
/* 2. ユーティリティが生成される(簡略化) */
.bg-mint-500 { background-color: var(--color-mint-500); }
.text-mint-500 { color: var(--color-mint-500); }
だから、HTML ではユーティリティとして使えますし、
<div class="bg-mint-500">...</div>
ユーティリティでは表現しづらい場面では、同じ値を CSS 変数として直接使えます。
<div style="background-color: var(--color-mint-500)">...</div>
「クラスでも変数でも、同じ 1 つの定義から使える」——これが v4 のテーマが強力な理由です。
5.4 名前空間と生成されるクラスの対応
@theme の変数は、適当な名前を付けるわけではありません。変数名の接頭辞(名前空間)が、どんなユーティリティを生成するかを決めます。主な対応は次のとおりです。
| 変数の名前空間 | 生成されるもの | 例 |
|---|---|---|
--color-* | 色のユーティリティ | bg-* text-* border-* fill-* |
--spacing-*(基準は --spacing) | 余白・サイズ | p-* m-* gap-* w-* |
--breakpoint-* | レスポンシブのバリアント | sm: md: lg: |
--font-* | フォントファミリ | font-* |
--text-* | フォントサイズ | text-sm text-lg |
--radius-* | 角丸 | rounded-* |
--shadow-* | 影 | shadow-* |
--animate-* | アニメーション | animate-* |
つまり、--color-brand: #1e40af; と書けば自動的に bg-brand や text-brand が使えるようになり、--breakpoint-tablet: 50rem; と書けば tablet: というレスポンシブのバリアントが使える、というように、名前空間さえ合わせればユーティリティが生えるわけです。この規則性を知っておくと、テーマの拡張が一気に分かりやすくなります。
5.5 拡張・上書き・リセット
テーマのカスタマイズには 3 つのパターンがあります。
(1) 拡張(既定値に追加する)
デフォルトのパレットやスケールを残したまま、新しい値を足します。@theme に新しい変数を書くだけです。
@theme {
--color-brand: oklch(0.45 0.24 264); /* 既存の色 + brand を追加 */
}
(2) 上書き(既定値を置き換える)
同じ名前の変数を再定義すると、その値だけ差し替わります。
@theme {
--breakpoint-lg: 70rem; /* lg の値を変更 */
}
(3) リセット(既定値を捨てて作り直す)
ある名前空間を一度まっさらにしたいときは、その名前空間に対して 初期化したい接頭辞-*: initial を指定してから定義し直します。たとえば「デフォルトの色をすべて捨て、自社パレットだけにする」なら --color-*: initial です。
@theme {
--color-*: initial; /* 色の名前空間だけをすべて消す */
--color-bg: oklch(1 0 0); /* 自社の色だけ定義 */
--color-fg: oklch(0.2 0 0);
}
なお、名前空間を限定せずに --*: initial と書くと、色だけでなくすべてのテーマ変数(余白・フォント・ブレークポイントなど)が一括でリセットされます。色だけ作り直したいときに --*: initial を使うと、余白やブレークポイントまで消えてしまうので注意してください。名前空間単位なら --color-*: initial、全体リセットなら --*: initial、と使い分けます。
リセットは強力ですが、gray-500 のような便利な既定色も消える点に注意してください。多くのプロジェクトでは「拡張」か「一部上書き」で十分です。
5.6 v3 の tailwind.config.js との対応関係と移行
v3 から来た人のために、対応関係を整理します。考え方は「theme.extend の中身を、CSS 変数の名前空間に置き換える」です。
// v3: tailwind.config.js
module.exports = {
theme: {
extend: {
colors: { brand: '#1e40af' },
spacing: { '128': '32rem' },
screens: { '3xl': '1920px' },
},
},
};
/* v4: CSS の @theme */
@theme {
--color-brand: #1e40af;
--spacing-128: 32rem;
--breakpoint-3xl: 120rem;
}
なお、既存の tailwind.config.js を v4 でもそのまま読み込みたい場合は、CSS 側で @config "../tailwind.config.js"; と書けば互換モードで動きます。大きな既存プロジェクトを少しずつ移行するときの逃げ道として用意されています(移行全体は第29章)。
5.7 spacing スケールと --spacing の動的計算(v4)
第4章で見た p-6 → calc(var(--spacing) * 6) を、ここで回収します。
v3 までは、p-1・p-2・p-3… といった余白の値が、一つひとつ個別に定義されていました(p-1 は 0.25rem、p-2 は 0.5rem…)。v4 では、これを 1 つの基準値 --spacing からの掛け算で表現します。
@theme {
--spacing: 0.25rem; /* 基準値。既定は 0.25rem(= 4px) */
}
この基準があると、p-6 は calc(var(--spacing) * 6)、m-2 は calc(var(--spacing) * 2) というように、任意の倍数を動的に計算できます。だから、わざわざ定義していない p-13 のような値も規則的に生成できますし、基準値を変えれば全体の余白感を一括で調整できます。第1部で「余白は --spacing を基準にした段階的な値」と表現したのは、この仕組みのことです。
5.8 色: oklch ベースの新パレットと P3
v4 のデフォルトの色パレットは、従来の 16 進数(#3b82f6)ではなく oklch という色空間で定義されています。
--color-red-500: oklch(63.7% 0.237 25.331);
なぜ oklch なのでしょうか。理由は 2 つあります。
- 知覚的に均等: oklch は「人間の目で見た明るさ・鮮やかさ」に近い形で色を表現します。
500→600→700と数字が上がるにつれて、見た目の暗さが素直に変化します。これは色のスケールを作るうえで扱いやすい性質です。 - より広い色域(P3)を表現できる: 16 進数(sRGB)では表現できない、より鮮やかな色を、対応するディスプレイで表示できます。新しい広色域ディスプレイの能力を活かせます。
実務上は「oklch という新しい書き方になった」程度の認識で問題ありませんが、自分でブランドカラーを定義するときも oklch で書くと、明度のステップを揃えやすくなります(色の詳細は第12章)。
5.9 実行時に CSS 変数を使う
5.3 で見たとおり、テーマ変数は本物の CSS 変数として出力されます。これは実務で地味に効いてきます。
- 素の CSS との連携: ユーティリティで書きにくい複雑なスタイルを CSS で書くとき、
var(--color-brand)でテーマの色を参照できます。色の定義が二重管理になりません。 - JavaScript からの参照: 実行時に
getComputedStyle(document.documentElement).getPropertyValue('--color-brand')のように、テーマの値を JavaScript から読めます。チャートライブラリに Tailwind の色を渡す、といった用途で便利です。 - 動的なテーマ切り替え: CSS 変数なので、
.darkクラスが付いたときに変数の値を差し替える、といったことも自然にできます(ダークモードは第18章)。
「デザイントークンが、Tailwind の中だけに閉じず、プロジェクト全体で使える共通言語になる」——これが v4 のテーマシステムが目指したゴールです。
参考資料
- Tailwind CSS Docs — Theme variables(@theme・名前空間・拡張/上書き/リセット)
- Tailwind CSS Docs — Colors(oklch パレット)
- Tailwind CSS Docs — Functions and directives(--spacing()・theme() ほか)
- Tailwind CSS v4.0(CSS-first config・theme variables)
第6章 バリアントの仕組み
ここまでで、ユーティリティが生成される仕組み(第4章)と、その値を決めるテーマ(第5章)を見てきました。残るピースがバリアントです。hover:bg-blue-700 の hover:、md:flex の md: のように、ユーティリティの前に付けて「どんな条件のときに効かせるか」を指定する接頭辞——これがバリアントです。第3章で「Tailwind がインラインスタイルと違う最大の理由」として挙げた、状態とレスポンシブを支える仕組みです。
6.1 バリアントとは
バリアントは、ユーティリティに「条件」を付けるものです。
<button class="bg-blue-500 hover:bg-blue-700">送信</button>
ここで bg-blue-500 は常に効く背景色、hover:bg-blue-700 はマウスを乗せたときだけ効く背景色です。生成される CSS を見ると、バリアントの正体がよく分かります(簡略化)。
.bg-blue-500 { background-color: var(--color-blue-500); }
.hover\:bg-blue-700:hover { background-color: var(--color-blue-700); }
hover: というバリアントは、要するに CSS の :hover セレクタを生成するための指示だったわけです。バリアントとは「ユーティリティを、特定のセレクタやメディアクエリでくるむためのルール」だと理解してください。インラインスタイル(style 属性)では :hover も @media も書けないので、これはユーティリティクラスならではの表現力です。
6.2 状態系のバリアント
ユーザーの操作や要素の状態に応じたバリアントです。よく使うものを挙げます。
<button class="bg-blue-500 hover:bg-blue-600 active:bg-blue-700 focus:ring-2 disabled:opacity-50">
送信
</button>
hover:… マウスが乗っているとき(:hover)focus:… フォーカスが当たっているとき(:focus)focus-visible:… キーボード操作などでフォーカスが当たったとき。マウスクリック時には出さない、アクセシビリティに配慮したフォーカス表示に使います(第21章)active:… 押されている最中(:active)disabled:… 無効化されているとき(:disabled)
いずれも、対応する CSS の擬似クラスに変換されているだけです。
6.3 構造系のバリアント
要素の位置や状態(何番目か、空かどうか)に応じたバリアントです。リストやテーブルで活躍します。
<ul>
<li class="border-b last:border-b-0 odd:bg-gray-50">項目</li>
</ul>
first:/last:… 最初 / 最後の子要素(:first-child/:last-child)odd:/even:… 奇数番目 / 偶数番目(:nth-child(odd)など)。表の縞模様(ゼブラ)に便利only:… 子要素が 1 つだけのときempty:… 中身が空のとき
6.4 グループ・ピア(group-* / peer-*)
ここからが、バリアントの真価が出るところです。「ある要素の状態に応じて、別の要素のスタイルを変えたい」という、素の CSS では少し書きづらいことを、宣言的に書けます。
group-*(親の状態に子が反応する)
親に group を付け、子に group-hover: などを付けると、親がホバーされたときに子のスタイルが変わります。カード全体にマウスを乗せたら、中のタイトルの色を変える、といった定番の表現です。
<a href="#" class="group block p-4">
<h3 class="text-gray-900 group-hover:text-blue-600">プロジェクト</h3>
<p class="text-gray-500 group-hover:text-gray-700">説明文</p>
</a>
peer-*(兄弟の状態に反応する)
peer を付けた要素の状態に、同じ階層の別の要素が反応します。フォームで「入力が不正なときにエラーメッセージを出す」といった用途が代表的です。
<input type="email" class="peer border" />
<p class="invisible peer-invalid:visible text-red-600">
有効なメールアドレスを入力してください
</p>
入力欄(peer)が :invalid のとき、後ろのメッセージが表示されます。JavaScript を書かずに、状態と見た目を連動させられるのがポイントです(フォームは第20章)。
6.5 メディア系のバリアント
画面や環境の条件に応じたバリアントです。中身は CSS のメディアクエリです。
- ブレークポイント(
sm:md:lg:xl:2xl:)… 画面幅に応じた切り替え(第17章で詳述) dark:… ダークモードのとき(第18章)motion-reduce:… ユーザーが「視差効果を減らす」設定にしているとき。過剰なアニメーションを抑えるために使います(第19章・第21章)print:… 印刷時
<div class="text-base md:text-lg dark:text-gray-200 print:text-black">...</div>
v4 のブレークポイントは、デフォルトで sm=40rem(640px)、md=48rem(768px)、lg=64rem(1024px)、xl=80rem(1280px)、2xl=96rem(1536px) です。生成されるメディアクエリは @media (width >= 48rem) のような新しい範囲記法になっています。
6.6 属性・データ系のバリアント
HTML の属性に応じてスタイルを当てるバリアントです。アクセシビリティ属性や、自前の状態管理と相性が良いものです。
<button aria-pressed="true" class="aria-pressed:bg-blue-600">トグル</button>
<div data-size="large" class="data-[size=large]:p-8">...</div>
aria-*:…aria-checkedやaria-pressedなどの ARIA 属性に反応。状態を見た目に反映できます(第21章)data-*:…data-属性に反応。data-[size=large]:p-8のように属性の値で分岐もできます。JavaScript フレームワークが要素に付ける状態(開いている/選択中など)と組み合わせると強力です
v4 では、data-* バリアントが標準でかなり柔軟に書けるようになりました。Stimulus(Rails)や各種 UI ライブラリが付与する data- 属性をそのままスタイリングのフックにできます。
6.7 has-* ・ not-* ・ *
比較的新しい CSS 機能に対応したバリアントです。
has-*(親が特定の子を持つとき)… CSS の:has()に対応。「中にチェック済みのラジオがあるラベルの背景を変える」など、これまで JavaScript が必要だった表現を CSS だけで書けます。
<label class="has-checked:bg-indigo-50 p-4 border rounded">
<input type="radio" name="plan" /> プラン A
</label>
not-*(v4)…:not()に対応。「ホバーされていないとき」のような否定条件を書けます。*(直接の子すべて)… 直接の子要素すべてに同じスタイルを当てます。*:(直接の子)や**:(すべての子孫)といった形があります。
これらは「素の CSS の進化を、ユーティリティのまま使える」ようにしたものです。Tailwind がモダン CSS の薄いラッパーである、という性格がよく表れています。
6.8 バリアントのスタック(評価順)
バリアントは重ねがけ(スタック)できます。dark: かつ md: 以上かつ hover: のとき、というように条件を組み合わせられます。
<button class="dark:md:hover:bg-fuchsia-600">保存</button>
これは「ダークモードで、画面が md 以上で、ホバーされているとき」に背景色を変える、という意味です。生成される CSS は、メディアクエリの入れ子と擬似クラスの組み合わせになります(簡略化)。
@media (prefers-color-scheme: dark) {
@media (width >= 48rem) {
.dark\:md\:hover\:bg-fuchsia-600:hover {
background-color: var(--color-fuchsia-600);
}
}
}
複雑に見えますが、左から順に条件を絞り込んでいるだけです。なお、書く順番は読みやすさのために「環境 → 状態」の順(dark:md:hover:)に揃えるチーム規約にしておくと、後から読みやすくなります。
6.9 カスタムバリアント(@custom-variant)
用意されたバリアントで足りないときは、自分でバリアントを定義できます。v4 では @custom-variant ディレクティブを使います。
@custom-variant pointer-coarse (@media (pointer: coarse));
これで pointer-coarse:p-6(指で操作するタッチデバイスのとき余白を広げる)のように使えます。
実は、v4 のダークモードを「手動切り替え(クラス方式)」にする設定も、このカスタムバリアントで書きます。
@custom-variant dark (&:where(.dark, .dark *));
こう書くと、dark: は「OS 設定」ではなく「.dark クラスが祖先に付いているとき」に効くようになります(第18章で詳しく扱います)。
ここまでで、Tailwind の三本柱——ユーティリティの生成(第4章)・テーマ(第5章)・バリアント(第6章)——がそろいました。この 3 つが分かれば、Tailwind の挙動はもう「魔法」ではありません。次の第3部では、これらを実際に動かすための環境構築に進みます。
参考資料
- Tailwind CSS Docs — Hover, focus, and other states(各種バリアント・スタック)
- Tailwind CSS Docs — Responsive design(ブレークポイント・max-* ・container queries)
- Tailwind CSS Docs — Dark mode(dark: ・@custom-variant dark)
- Tailwind CSS Docs — Functions and directives(@custom-variant)
演習(第2部)
仕組みは「実際に生成 CSS を見る」のが最良の理解法です。Tailwind Play(https://play.tailwindcss.com/)を開いて試しましょう。
- 生成 CSS を見よう: Play で
<div class="p-6 text-center">と書き、生成された CSS を確認してください。p-6がcalc(var(--spacing) * 6)になっていることを自分の目で確かめましょう(§4.1・§4.9)。 - テーマを足そう: Play の CSS 欄に
@theme { --color-brand: oklch(0.45 0.24 264); }を追加し、bg-brandが使えるようになることを確認してください(§5.2〜5.4)。 - バリアントを重ねよう:
class="bg-blue-500 hover:bg-blue-700 md:bg-green-500"の要素を作り、ホバー時・画面幅を変えたときの挙動を観察してください。生成された CSS にメディアクエリと:hoverがどう現れるか見てみましょう(第6章)。
第3部 環境構築
第2部までで、Tailwind CSS が「なぜ」「どう」動くのかを理解しました。ここからは、実際に手を動かすための環境構築です。
環境構築は、Tailwind でつまずく人が最も多い場所でもあります。理由は単純で、Tailwind は「どのビルドツールの上で動かすか」によって導入手順が変わるからです。Vite で動かすのか、Rails のアセットパイプラインで動かすのか——土台が違えば、入れ方も変わります。
そこでこの第3部では、まず導入経路の全体像と共通の作法を押さえ(第7章)、次に主要なフレームワークごとの具体的な手順を見ます(第8章、Rails を最重点で扱います)。最後に、補完や整形といったエディタ環境を整え、開発を快適にします(第9章)。
とにかく今すぐ動かしたい人へ: 理屈は後回しでまず「画面に青い文字を出す」体験をしたいなら、先に 付録F ハンズオン: ゼロから動く最小例へ進んでください。ビルド不要の経路(Play CDN)なら 1 分で動きます。一度動かしてから本章に戻ると、各手順の意味がぐっと分かりやすくなります。
本書は Tailwind CSS v4 を前提としています。 v4 では読み込みが
@import "tailwindcss";の 1 行になり、tailwind.config.jsは必須ではなくなりました。古い記事に出てくる@tailwind base; @tailwind components; @tailwind utilities;という 3 行の書き方は v3 までのものです。本書では使いません。
第7章 Tailwind CSS の導入方法
7.1 導入経路の地図
Tailwind の導入経路は、大きく次の 4 つです。最初にこの地図を持っておくと、自分が何を選んでいるのかを見失いません。
| 経路 | 何のためか | 向いている人 |
|---|---|---|
| Play CDN | ビルドなしでブラウザだけで試す | とにかく今すぐ試したい・学習用 |
| Vite プラグイン | Vite を使うプロジェクトに組み込む | React / Vue / Svelte など Vite ベースのほとんど |
| PostCSS プラグイン | PostCSS を使うビルドに組み込む | Next.js や、PostCSS 前提のツール |
| Tailwind CLI | Tailwind 単体で CSS をビルドする | ビルドツールを増やしたくない・小規模 |
このうち、本番プロジェクトで主役になるのは Vite プラグインです。v4 で最も手軽かつ高速な経路として公式が推奨しています。Rails のような独自のアセットパイプラインを持つフレームワークは、これとは別に専用の経路(gem)が用意されています(第8章)。
7.2 最速の入口: Play CDN と Tailwind Play
「まず Tailwind がどんなものか触りたい」なら、ビルド環境を作らずに始められる方法が 2 つあります。
(1) Play CDN(自分の HTML ファイルで試す)
HTML の <head> に次のスクリプトを 1 行入れるだけで、その場でユーティリティが効くようになります。
<!doctype html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>
<body>
<h1 class="text-3xl font-bold underline">Hello world!</h1>
</body>
</html>
これは v4 のパッケージ @tailwindcss/browser を使う方法です。ブラウザ上でその場で CSS を生成するため、開発・学習用と割り切ってください(本番には使いません。ページ表示のたびに生成コストがかかります)。テーマも <style type="text/tailwindcss"> の中に @theme { ... } を書けば試せます。
(2) Tailwind Play(ブラウザ上のエディタ)
ファイルすら用意したくないなら、公式のオンライン環境 Tailwind Play(https://play.tailwindcss.com/)が便利です。左に HTML、右にプレビューが出て、生成された CSS も確認できます。第2部で学んだ「どんな CSS が生成されるか」を確かめるのにも最適です。
7.3 推奨: @tailwindcss/vite での導入手順
ここからが実プロジェクト向けです。まずは推奨経路である Vite から見ます。手順は 3 ステップです。
ステップ 1: パッケージをインストールする
npm install tailwindcss @tailwindcss/vite
ステップ 2: Vite に Tailwind プラグインを登録する
vite.config.ts(または .js)にプラグインを追加します。
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [
tailwindcss(),
],
})
ステップ 3: CSS で Tailwind を読み込む
プロジェクトの CSS エントリ(たとえば src/style.css)に、次の 1 行を書きます。
@import "tailwindcss";
あとは npm run dev で開発サーバーを起動すれば完了です。第2部で見たとおり、この時点で content 配列のような走査対象の指定は不要です(v4 の自動コンテンツ検出)。
7.4 PostCSS(@tailwindcss/postcss)での導入
ビルドが PostCSS を前提にしている場合(代表例は次章の Next.js)は、PostCSS プラグインを使います。
ステップ 1: インストール
npm install tailwindcss @tailwindcss/postcss postcss
ステップ 2: PostCSS の設定ファイルを作る
postcss.config.mjs を作成します。
export default {
plugins: {
"@tailwindcss/postcss": {},
},
}
ステップ 3: CSS で読み込む
@import "tailwindcss";
v3 を知っている人へ: v3 では
postcss.config.jsにtailwindcssとautoprefixerを並べて書くのが定番でした。v4 では、ベンダープレフィックス付与などを内蔵の Lightning CSS が担うため(第4章)、autoprefixerを別途書く必要はありません。
7.5 CLI(@tailwindcss/cli)での導入とウォッチ
「Vite も PostCSS も使いたくない、Tailwind だけで CSS を作りたい」場合は、Tailwind CLI を使います。
ステップ 1: インストール
npm install tailwindcss @tailwindcss/cli
ステップ 2: 入力 CSS を用意する
/* src/input.css */
@import "tailwindcss";
ステップ 3: ビルド(ウォッチ)する
入力ファイルを指定し、出力先を決めて実行します。--watch を付けると、ファイルの変更を監視して自動で再ビルドします。
npx @tailwindcss/cli -i ./src/input.css -o ./src/output.css --watch
生成された output.css を HTML から読み込めば完成です。
<link href="./output.css" rel="stylesheet">
CLI は仕組みが分かりやすいので、「Tailwind が実際に何をしているか」を理解する学習用途にも向いています。
7.6 @import "tailwindcss" と最小の CSS エントリ
ここまでのどの経路でも、CSS エントリは次の 1 行から始まります。
@import "tailwindcss";
第4章で見たとおり、この 1 行が theme / base / components / utilities の各レイヤーを読み込みます。あなた独自のスタイルやテーマは、この下に足していきます。
@import "tailwindcss";
/* テーマのカスタマイズ(第5章) */
@theme {
--color-brand: oklch(0.45 0.24 264);
}
/* 必要なら自前のスタイルやカスタムユーティリティ(第22章・第26章) */
この「@import してから、必要なものを足す」という形が、v4 のすべてのプロジェクトの出発点になります。
7.7 v3 からの移行ツール(@tailwindcss/upgrade)の概要
既存の v3 プロジェクトを v4 に上げるなら、公式の移行ツールが用意されています。
npx @tailwindcss/upgrade
このツールは、面倒な変換の大部分を自動でやってくれます。具体的には、
@tailwind base; @tailwind components; @tailwind utilities;を@import "tailwindcss";に置き換えるtailwind.config.jsの内容を、可能な範囲で CSS の@themeに移す- 名前が変わったユーティリティを書き換える(例:
shadow-sm→shadow-xs、outline-none→outline-hidden)
といったことを行います。実行には Node.js 20 以上が必要です。なお、v4 はモダンブラウザ(Safari 16.4+ / Chrome 111+ / Firefox 128+ など)を対象としているため、古いブラウザを手厚くサポートする必要があるプロジェクトは v3.4 に留まる判断もありえます。移行の詳細や破壊的変更は第29章でまとめて扱います。
7.8 よくある初回トラブル(クラスが効かない・検出されない)
最後に、導入直後にハマりがちな問題と対処をまとめます。多くは第2部の仕組みを思い出せば解決できます。
(1) クラスを書いたのにスタイルが効かない
最も多い原因は、クラス名を動的に組み立てていることです。第4章で見たとおり、Tailwind はソースを「ただのテキスト」として走査し、完全なクラス名の文字列だけを拾います。
// NG: text-${color} は文字列として検出されない
<div className={`text-${color}-600`} />
// OK: 完全なクラス名で書く
<div className={color === 'error' ? 'text-red-600' : 'text-green-600'} />
この落とし穴は第27章でも改めて扱います。
(2) 一部のファイルのクラスだけ効かない
自動コンテンツ検出の対象外(.gitignore 配下、node_modules 内のライブラリなど)にあるファイルのクラスは拾われません。その場合は CSS 側で @source を足して、走査対象に含めます(第4章)。
@import "tailwindcss";
@source "../node_modules/my-ui-library";
(3) そもそも何も効かない
CSS エントリに @import "tailwindcss"; を書き忘れている、あるいはその CSS をページから読み込んでいない、というケースです。また、古い記事を見て @tailwind base; ... と書いてしまうのも v4 では動きません。@import "tailwindcss"; に直してください。
(4) ビルドは通るが、変更が反映されない
ウォッチ(監視)プロセスが動いていない可能性があります。npm run dev や CLI の --watch、Rails なら bin/dev(次章)が起動しているか確認します。
参考資料
- Tailwind CSS Docs — Installation: using Vite
- Tailwind CSS Docs — Installation: using PostCSS
- Tailwind CSS Docs — Installation: Tailwind CLI
- Tailwind CSS Docs — Play CDN
- Tailwind CSS Docs — Upgrade guide(v3 → v4)
- Tailwind Play
第8章 フレームワークごとの導入
8.1 導入差が生まれる理由
第7章で「導入経路は土台によって変わる」と述べました。その理由をはっきりさせておきましょう。
Tailwind がやることは、突き詰めれば「CSS をビルドして 1 枚のスタイルシートを出力する」ことです。問題は、そのビルドを誰が回すかがフレームワークごとに違う、という点です。
- React・Vue・Svelte などは、たいてい Vite がビルドを回します → Vite プラグイン経路(第7章)。
- Next.js は内部で PostCSS を使います → PostCSS プラグイン経路。
- Rails は 独自のアセットパイプライン(Propshaft など)を持っています → Rails 専用の gem 経路。
つまり「Tailwind の入れ方の違い」は、ほとんど「そのフレームワークが CSS をどうビルドするかの違い」です。この視点を持つと、以下の各手順がなぜそうなっているのか腑に落ちます。それでは、本書が最重点とする Rails から詳しく見ていきます。
8.2 Rails 詳説 ①: tailwindcss-rails gem と最短セットアップ
Rails で Tailwind を使う最も標準的な方法は、tailwindcss-rails という gem を使うことです。これは Tailwind Labs ではなく Rails チームがメンテナンスしている公式 gem で、Tailwind 公式の Rails ガイドからもこの gem が案内されています。
最大の特徴は、Node.js を必要としないことです。tailwindcss-rails は tailwindcss-ruby gem を通じて、Tailwind のスタンドアロン実行ファイル(単体で動く CLI のバイナリ)を取得して使います。そのため、npm も package.json も node_modules も要りません。Rails らしく、Ruby の世界だけで完結します。これが、第7章で見た JavaScript 系の経路と決定的に違う点です。
セットアップは、新規 Rails アプリなら次の手順です(Rails 8 以降を前提とします)。
# 新規アプリを作る
rails new my-project
cd my-project
# gem を追加して、インストールタスクを実行する
bundle add tailwindcss-rails
./bin/rails tailwindcss:install
tailwindcss:install を実行すると、必要なファイルが自動で用意されます。具体的には、入力 CSS(後述の app/assets/tailwind/application.css)が作られ、レイアウトで生成済み CSS を読み込めるように読み込み設定が必要に応じて整えられ(既定レイアウトにすでに読み込みがある場合はそのまま使われます)、開発用の Procfile.dev などが用意されます。
新規作成の段階で決めるなら、
rails new my-project --css tailwindのように--css tailwindを付けると、最初から Tailwind 入りのアプリが作られます。
8.3 Rails 詳説 ②: 入力 CSS・出力・ウォッチ(bin/dev)の関係
ここが Rails 利用者が最も混乱しやすいところなので、「入力 → ビルド → 出力 → 読み込み」の流れとして整理します。
入力 CSS の場所
あなたが Tailwind の設定やカスタムスタイルを書くファイルは、ここです。
app/assets/tailwind/application.css
中身は v4 の作法どおり、@import "tailwindcss"; から始まります。
@import "tailwindcss";
@theme {
--color-brand: oklch(0.45 0.24 264);
}
v3 を知っている人へ: このファイルの場所は v4 で変わりました。v3(gem の v3 系)では
app/assets/stylesheets/application.tailwind.cssでしたが、v4 ではapp/assets/tailwind/application.cssです。古い記事のパスのままだと動かないので注意してください。
ビルドと出力
tailwindcss-rails は、上の入力 CSS を読んで、生成済みの CSS を app/assets/builds/ に出力します。Rails のアセットパイプライン(Propshaft)は、その出力済み CSS を配信します。ビルドを手動で 1 回だけ実行したいときは次のコマンドです。
./bin/rails tailwindcss:build
ウォッチ(開発中の自動再ビルド)
開発中は、CSS を書き換えるたびに手でビルドするのは現実的ではありません。そこで「ファイルの変更を監視して自動で再ビルドする」ウォッチを使います。方法は主に 3 つあります。
-
bin/dev(最も一般的): Rails サーバーと Tailwind のウォッチを同時に起動してくれるコマンドです。内部ではProcfile.devに書かれた複数プロセス(Web サーバー+tailwindcss:watch)を Foreman などでまとめて動かしています。開発時はこれを使うのが基本です。./bin/dev -
tailwindcss:watchを単体で動かす: ウォッチだけを別プロセスで動かしたいときに使います。./bin/rails tailwindcss:watch -
Puma プラグインで
rails serverに統合する:bin/devを使わず、rails serverの起動に合わせてウォッチを走らせる構成も用意されています。
整理すると、「app/assets/tailwind/application.css に書く → bin/dev が変更を監視して app/assets/builds/ に出力 → Propshaft がそれを配信する」という一本道です。この流れさえ押さえれば、Rails での Tailwind は迷いません。
8.4 Rails 詳説 ③: Propshaft / Sprockets と cssbundling 経路の使い分け
Rails には CSS の扱い方が複数あり、ここも混乱の元なので関係を整理します。
アセットパイプライン: Propshaft と Sprockets
アセットパイプラインは「CSS や画像などの静的ファイルを配信する仕組み」です。Rails 8 以降の既定は Propshaft という軽量な仕組みで、古くからの Sprockets に代わるものです。tailwindcss-rails はどちらとも組み合わせて動きますが、新規プロジェクトでは Propshaft が標準だと考えてよいでしょう。重要なのは、Tailwind 自体のビルドは gem(スタンドアロン実行ファイル)が行い、その成果物をアセットパイプラインが配信する、という分担関係です。
もう 1 つの選択肢: cssbundling-rails
tailwindcss-rails とは別に、cssbundling-rails という gem を使う道もあります。これは Node.js と package.json を使い、Tailwind・PostCSS・Dart Sass・Bootstrap・Bulma といった CSS 処理を yarn build:css で実行して、app/assets/builds/application.css に出力する方式です(JavaScript のバンドルを担う jsbundling-rails とは別物で、こちらは CSS 専用です)。
両者の使い分けの目安は次のとおりです。
tailwindcss-rails | cssbundling-rails | |
|---|---|---|
| Node.js / package.json | 不要 | 必要 |
| 仕組み | スタンドアロン実行ファイル | yarn build:css で CSS を処理 |
| 向いているケース | Tailwind だけ使えればよい。構成をシンプルにしたい | すでに Node ベースの JS ビルドがあり、CSS も同じ流れに乗せたい |
多くの Rails プロジェクトでは、Node 不要でシンプルな tailwindcss-rails が第一候補です。フロントエンドを JavaScript ツールで本格的にバンドルしている場合に限り、cssbundling-rails を検討する、という順序がおすすめです。
8.5 Rails 詳説 ④: importmap 構成での注意点と Turbo との相性
最後に、JavaScript の管理方式である importmap との関係です。
ここで誤解を解いておきます。importmap は JavaScript を管理する仕組みであって、CSS には関与しません。Rails 8 の既定は「JS は importmap、CSS は tailwindcss-rails」という組み合わせで、両者は別々に動きます。つまり「importmap を使っているから Tailwind が入れにくい」ということはなく、tailwindcss-rails がそのまま使えます。むしろ「Node 不要の importmap + Node 不要の tailwindcss-rails」は、Rails らしいシンプルな構成として相性が良い組み合わせです。
Turbo との相性
Rails の標準フロントエンドである Turbo(Hotwire)は、ページ遷移を高速化するために、ページ全体を読み込み直さず差し替えます。このとき気をつけたいのは、Tailwind の CSS はビルド時にすべて生成されているという点です。第4章で見たとおり、Tailwind はソースを静的に走査して CSS を作るので、Turbo が後から差し込む HTML に使われているクラスも、そのクラスがソースコードのどこかに文字列として存在していれば、ちゃんとスタイルが当たります。逆に言えば、ここでも「動的に組み立てたクラス名は生成されない」という原則は同じです。Turbo で部分更新される画面でも、クラス名は完全な文字列で書く、という基本を守れば問題は起きません。
8.6 React(Vite)での導入
ここからは JavaScript 系フレームワークです。まず Vite で作った React プロジェクトは、第7章の Vite 経路がそのまま使えます。
npm install tailwindcss @tailwindcss/vite
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [react(), tailwindcss()],
})
/* src/index.css */
@import "tailwindcss";
この CSS をエントリ(main.tsx など)でインポートすれば完了です。JSX では class ではなく className を使う点だけ注意してください。
export default function App() {
return <h1 className="text-3xl font-bold underline">Hello world!</h1>
}
8.7 Next.js(App Router)での導入と注意点
Next.js は内部で PostCSS を使うため、第7章の PostCSS 経路を使います。
npm install tailwindcss @tailwindcss/postcss postcss
// postcss.config.mjs
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
}
export default config
/* app/globals.css */
@import "tailwindcss";
この globals.css を app/layout.tsx でインポートします(App Router の標準構成)。
// app/layout.tsx
import "./globals.css"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ja">
<body>{children}</body>
</html>
)
}
注意点: Next.js の App Router では、コンポーネントがサーバーコンポーネントとして動くことがあります。いずれの場合も「クラス名を動的に組み立てない」という原則は共通です(理由と対処は §27.3、React 側の具体策は第25章)。
8.8 Vue / Nuxt での導入
Vue(Vite ベース)は React と同じく Vite 経路です。@tailwindcss/vite を vite.config.ts に登録し、CSS で @import "tailwindcss"; を読み込みます。
Nuxt は Vite を内部で使っているため、Nuxt の設定に Vite プラグインを組み込む形になります。具体的な組み込み方は公式の Nuxt ガイドに沿うのが確実です(参考資料のリンク先)。テンプレート(.vue ファイル)に書いたクラスは、自動コンテンツ検出の対象になります。
8.9 Svelte / SvelteKit での導入
Svelte / SvelteKit も Vite ベースなので、Vite 経路を使います。@tailwindcss/vite を Vite の設定に追加し、アプリのグローバル CSS で @import "tailwindcss"; を読み込みます。.svelte ファイル内のクラスは自動で検出されます。詳細な配線は公式の SvelteKit ガイドが最新です。
8.10 Astro での導入
Astro も内部で Vite を使うため、@tailwindcss/vite を Astro の設定(astro.config.mjs)の Vite プラグインとして組み込み、グローバル CSS で @import "tailwindcss"; を読み込みます。Astro は公式の導入ガイドが用意されているので、最新の手順はそちらに従ってください(参考資料)。
8.11 早見表(各環境の設定ファイル・エントリ・ウォッチ方法)
最後に、本章の内容を 1 枚にまとめます。
| 環境 | 経路 | 設定を書く場所 | CSS エントリ | 開発時の起動 |
|---|---|---|---|---|
| Rails | gem(Node 不要) | app/assets/tailwind/application.css | 同左(@import) | ./bin/dev |
| React (Vite) | Vite プラグイン | vite.config.ts | src/index.css | npm run dev |
| Next.js | PostCSS プラグイン | postcss.config.mjs | app/globals.css | npm run dev |
| Vue / Nuxt | Vite プラグイン | Vite/Nuxt 設定 | グローバル CSS | npm run dev |
| Svelte / SvelteKit | Vite プラグイン | Vite 設定 | グローバル CSS | npm run dev |
| Astro | Vite プラグイン | astro.config.mjs | グローバル CSS | npm run dev |
どの環境でも、CSS エントリが @import "tailwindcss"; で始まる点と、自動コンテンツ検出が効く点は共通です。違うのは「ビルドを誰が回すか」だけ——この章の出発点に戻ってきました。
参考資料
- Tailwind CSS Docs — Framework guides(各フレームワークの一覧)
- Tailwind CSS Docs — Ruby on Rails ガイド
- tailwindcss-rails(GitHub・README / releases)
- Tailwind CSS Docs — Next.js ガイド
- Tailwind CSS Docs — Nuxt ガイド
- Tailwind CSS Docs — SvelteKit ガイド
- Tailwind CSS Docs — Astro ガイド
第9章 エディタ環境
導入が終わったら、開発を快適にするエディタ環境を整えます。Tailwind は「クラスを大量に書く」スタイルなので、補完や整形といった支援があるかどうかで、開発体験が大きく変わります。ここを整えるかどうかは、Tailwind を「面倒」と感じるか「快適」と感じるかの分かれ目になります。
9.1 VS Code: Tailwind CSS IntelliSense
VS Code を使うなら、公式拡張 Tailwind CSS IntelliSense は必ず入れましょう。主な機能は次のとおりです。
- オートコンプリート:
bg-まで打つと候補が一覧で出ます。膨大なユーティリティを暗記する必要がなくなります。テーマで定義した独自の色やスケールも候補に出ます。 - ホバープレビュー: クラスにマウスを乗せると、それが生成する実際の CSS が表示されます。第2部で学んだ「クラス → CSS」の対応を、その場で確認できます。
- 色プレビュー:
bg-red-500のような色クラスの横に、実際の色が表示されます。 - 構文ハイライト・リンティング: 存在しないクラスや、競合するクラス(後述)を指摘してくれます。
この拡張は、補完だけでなく「間違いに早く気づく」ためにも効きます。
9.2 クラス順の自動整形: prettier-plugin-tailwindcss
ユーティリティを並べていると、書く人によってクラスの順番がバラバラになります。p-4 flex bg-white と書く人もいれば flex bg-white p-4 と書く人もいます。これはレビューを読みにくくし、無用な差分を生みます。
公式の Prettier プラグイン prettier-plugin-tailwindcss を使うと、保存時にクラスを推奨される一定の順序に自動で並べ替えてくれます。
npm install -D prettier prettier-plugin-tailwindcss
// .prettierrc(または prettier.config.js)
export default {
plugins: ["prettier-plugin-tailwindcss"],
}
チーム全員が同じ順序になるため、「クラスの並べ方」で悩む時間と、レビューでの無駄な指摘がなくなります。これはチーム開発でとくに価値が大きい設定です(第26章でも触れます)。
補足: このプラグインは Prettier のものなので、Node を使わない Rails プロジェクトでは、整形のためだけに Prettier を導入するか判断が必要です。「整形のためだけに Node を入れたくない」なら、後述の IntelliSense によるクラス並び替え支援や、チームの命名ルールで運用する方法もあります。
9.3 重複・衝突の検知
Tailwind CSS IntelliSense は、衝突するクラスも警告してくれます。たとえば p-4 と p-8 を同じ要素に書くと、片方は無意味になります(第4章の「衝突の解決」)。こうした「効かないクラス」を書いてしまったとき、エディタ上で気づけるのは大きな助けです。動的にクラスを組み立てる React などでは、最終的に第23章で扱う tailwind-merge のような仕組みで衝突を解消しますが、まずはエディタの警告で早期に気づくのが第一歩です。
9.4 ディレクティブの「unknown at-rule」警告への対処
Tailwind は @theme・@source・@utility・@custom-variant といった独自のディレクティブを使います(第2部)。ところが、エディタの標準的な CSS チェッカーはこれらを知らないため、「unknown at-rule(未知のアットルール)」という警告を出すことがあります。これは Tailwind 側の誤りではなく、エディタが新しい構文を知らないだけです。
対処は次のとおりです。
- Tailwind CSS IntelliSense を使う: この拡張には、Tailwind の独自ディレクティブをすべて理解する専用の言語モードが含まれており、CSS ファイルを Tailwind の言語モードとして扱うことで警告が消えます。
- 標準の CSS リンティングを調整する: エディタが厳しく構文チェックしている場合は、その CSS の検証を無効化・緩和する設定を行います。
「赤い波線が出るが実害はない」状態を放置すると、本当のエラーを見逃しやすくなります。早めに解消しておきましょう。
9.5 JetBrains 系・Neovim 等での対応状況
Tailwind の支援は VS Code だけのものではありません。公式の言語サーバー(IntelliSense の中核)をベースに、さまざまなエディタが対応しています。
- JetBrains 系(RubyMine・WebStorm など): Tailwind の補完に対応しています。Rails 開発で RubyMine を使う人も、補完の恩恵を受けられます。
- Cursor・Zed: これらのエディタでも Tailwind の支援が利用できます。
- Neovim など: 言語サーバーを介して補完を有効にできます。
公式ドキュメントの Editor setup ページに、対応エディタと設定方法がまとまっています(参考資料)。
9.6 補完を効かせる設定
最後に、補完を最大限効かせるための小さなコツです。
- クラスを書く場所を教える: 文字列の中にクラスを書く独自のヘルパー(たとえば
cn(...)や、Rails のヘルパーメソッド)を使っている場合、その関数の中でも補完が効くように、IntelliSense に「ここにもクラスが書かれる」と設定できます。 - CSS の言語モードを合わせる: 9.4 のとおり、Tailwind の CSS ファイルは Tailwind の言語モードとして扱うと、ディレクティブの補完と警告抑制の両方が効きます。
ここまでで、Tailwind を実プロジェクトで動かし、快適に書くための土台が整いました。次の第4部からは、いよいよ個々のユーティリティ——余白・文字・色・レイアウトなど——を、第2部で学んだ仕組みの理解の上に学んでいきます。
参考資料
- Tailwind CSS Docs — Editor setup(IntelliSense・言語モード・警告対処)
- Tailwind CSS IntelliSense(GitHub)
- prettier-plugin-tailwindcss(GitHub)
演習(第3部)
読むだけでなく、実際に環境を作るのがこの部の演習です。付録F のハンズオンと合わせて取り組んでください。
おすすめの順序: 初めてなら、まず付録F で「動く状態」を作ってから(5 分)、第7章〜第9章を読み返して「なぜその設定が要るのか」を理解し、最後に下の演習に取り組むと、各手順の意味が腑に落ちます。
- 動かそう: 付録F を参考に、好きな経路(Play CDN / Rails / Vite)で「Hello, Tailwind!」を青い太字で表示させてください。表示できたら
text-blue-600を別の色に変え、反映されることを確認しましょう。 - トラブルを再現しよう: わざと
@import "tailwindcss";を消して(または@tailwind base;という古い書き方にして)、スタイルが効かなくなることを確認してください。原因と直し方を §7.8 で確認しましょう。 - エディタを整えよう: VS Code を使っているなら Tailwind CSS IntelliSense を入れ、
bg-と打って補完が出ること、クラスにホバーすると生成 CSS が見えることを確認してください(第9章)。
第4部 Utility Class を使う
ここからは、いよいよ個々のユーティリティクラスを学びます。ただし本書はリファレンス(クラス一覧表)ではありません。クラスの全網羅は公式ドキュメントに任せ、本書は次の 3 つを各章で一貫して扱います。
- 素の CSS ではどう考えるか(前提となる CSS の考え方)
- Tailwind ではどう対応するか(クラスの形と、生成される CSS)
- 実務でどう使い分けるか(一貫性を保つ判断、任意の値の乱用を避ける勘所)
そして、第2部で学んだ仕組み——--spacing を基準にした動的計算、oklch の色、任意の値、container query——に何度も立ち返ります。クラスを暗記するのではなく、「この値はテーマのどこから来ているのか」を理解することが、第4部のゴールです。
各章共通の注意(任意の値について): Tailwind は
p-[13px]のように角かっこで任意の値を書けます(第4章)。これは「スケールにない値がどうしても必要なとき」の便利な逃げ道ですが、多用するとスケールという制約(第3章)が崩れ、デザインの一貫性が失われます。各章で「ここぞ」という使いどころと、避けるべき乱用を示します。
第10章 Spacing
10.1 spacing スケールの思想
余白は、デザインの印象を最も大きく左右する要素のひとつです。素の CSS では padding: 16px のように好きな値を書けますが、第3章で見たとおり、この自由さがチーム開発では余白のばらつきを生みます。
Tailwind は、余白をスケール(段階的な値の並び)として提供します。そして v4 では、このスケールが第5章で見た --spacing というたった 1 つの基準値から計算されます。
/* p-4 が生成する CSS(簡略化) */
.p-4 { padding: calc(var(--spacing) * 4); }
--spacing の既定値は 0.25rem(= 4px)です。つまり p-4 は 0.25rem × 4 = 1rem(16px)になります。数字の 4 が「4px」という意味ではなく、「基準値の 4 倍」だという点が重要です。この仕組みのおかげで、p-4・p-6・p-8 のように飛び飛びの値が自然と 4px 刻みで揃い、誰が書いても余白の歩幅がそろいます。
10.2 padding
要素の内側の余白が padding です。クラスの形は方向ごとに分かれています。
<div class="p-4">全方向に内側余白</div>
<div class="px-6 py-3">左右に 6、上下に 3</div>
<div class="pt-8">上だけ 8</div>
p-*… 全方向(padding)px-*/py-*… 横(padding-inline)/ 縦(padding-block)pt-*pr-*pb-*pl-*… 上下左右の個別
v4 では、横方向が padding-left/right ではなく padding-inline という論理プロパティで生成される点も覚えておきましょう。これは「文字の流れる方向」に沿った指定で、日本語・英語のような左横書きでは左右に対応しますが、アラビア語のような右横書き(RTL)では自動で左右が反転します。多言語対応を見据えた設計です。文字方向に合わせて始端・終端を指定したいときは、論理プロパティ版の ps-*(始端)・pe-*(終端)も使えます。
なお、padding にマイナスの値はありません。内側余白を負にすることはできないからです。
10.3 margin と負のマージン
要素の外側の余白が margin です。形は padding と同じく m-* mx-* mt-* などです。padding と違い、margin にはマイナスがあります。
<div class="mt-6">上に外側余白</div>
<div class="-mt-2">上に負のマージン(上に食い込ませる)</div>
-mt-2 のように先頭に - を付けると負のマージンになります。重なりを作る、親の padding を打ち消す、といった用途で使います。ただし負のマージンはレイアウトを直感に反して崩しやすいので、多用は禁物です。「重ねたい」意図が明確なときだけに留めましょう。
10.4 要素間の間隔: space-x/y-* と gap-* の違い
「子要素どうしの間隔」を空けたい場面はとても多くあります。Tailwind には方法が 2 つあり、使い分けがよく問われます。
gap-*(推奨): Flexbox や Grid のコンテナに付けて、子の間の溝を空けます。
<div class="flex gap-4">
<button>保存</button>
<button>キャンセル</button>
</div>
space-x-* / space-y-*: 隣り合う子要素の間に、片側マージンで間隔を入れます(隣接する要素どうしの間だけに溝ができるイメージです)。
<div class="space-y-4">
<p>段落1</p>
<p>段落2</p>
</div>
どちらを使うべきか。 現在は gap-* が第一候補です。gap は CSS Grid / Flexbox の正式な機能で、要素を折り返したときも間隔が正しく保たれ、子要素を入れ替えても破綻しません。space-* はマージンを使うため、子の順序や折り返しで意図せぬ余白が出ることがあります。Flex/Grid コンテナなら gap、それ以外でやむを得ないときだけ space-*、と覚えておけば十分です。
10.5 任意の値とスケール外の値
どうしてもスケールにない余白が必要なら、任意の値が使えます。
<div class="p-[18px]">スケールにない 18px</div>
ただし、これを安易に使うのは黄色信号です。p-[18px] が現れるのは、たいてい「デザインがスケールに沿っていない」サインです。本当にその値でなければならないのか、p-4(16px)や p-5(20px)で代用できないかを先に疑ってください。どうしても必要なら、それをテーマ変数として定義し、名前を付けて再利用する(第5章・第26章)方が、1 回限りの [18px] を散らすより健全です。
10.6 実務: 一貫した余白設計とアンチパターン
実務では、「余白の値を決める」のではなく「余白の歩幅を決める」と考えると、設計がぶれません。たとえば「セクション間は py-16、カード内は p-6、要素間は gap-4」のように、役割ごとに使う値をチームで決めておきます。
避けたいアンチパターンは、画面ごとに余白がバラバラになることです。p-[13px]・mt-[7px]・gap-[5px] のような任意の値が散らばり始めたら、スケールから外れた設計の崩れを疑うべきサインです(詳しくは第27章)。
参考資料
第11章 Typography
11.1 フォントサイズ・行間
文字は情報そのものです。読みやすさは、フォントサイズと行間(line-height)の組み合わせで決まります。素の CSS では font-size と line-height を別々に指定しますが、Tailwind では text-* ひとつで両方をいい感じに設定してくれます。
<p class="text-base">本文(既定の行間付き)</p>
<h1 class="text-4xl">見出し</h1>
v4 のサイズスケールは text-xs(12px)から text-9xl(128px)まで段階的に並んでいます。text-base(16px)を基準に、sm・lg・xl・2xl… と広がります。それぞれに、読みやすい既定の行間がセットで定義されているのがポイントです。サイズを選ぶだけで、行間も自動でついてきます。
行間を自分で調整したいときは、text-{サイズ}/{行間} の形でまとめて書けます。
<p class="text-sm/6">サイズは sm、行間は 6(= 1.5rem)</p>
<p class="text-lg/7">サイズは lg、行間は 7</p>
スラッシュの後ろの数字は、--spacing ベースの行間です。本文を読みやすくしたいときは、行間を少し広めに取るのが定石です。
11.2 フォントファミリ・ウェイト・字間
- フォントファミリ:
font-sans・font-serif・font-mono。独自のフォントは第5章のとおり@themeに--font-display: ...のように定義するとfont-displayが使えます。 - ウェイト(太さ):
font-normal・font-medium・font-boldなど。見出しはfont-bold、本文はfont-normal、が基本です。 - 字間(letter-spacing):
tracking-tight・tracking-normal・tracking-wide。大きな見出しは少し詰める(tracking-tight)と引き締まって見えます。
<h1 class="font-sans text-4xl font-bold tracking-tight">締まった見出し</h1>
11.3 行揃え・装飾・変形
文字の見た目を整える基本のユーティリティです。素の CSS の text-align・text-decoration・text-transform にそのまま対応します。
<p class="text-center underline uppercase">中央・下線・大文字</p>
- 行揃え:
text-lefttext-centertext-righttext-justify - 装飾:
underlineline-throughno-underline - 変形:
uppercaselowercasecapitalize
11.4 行数制限(line-clamp)
カードの説明文などで「3 行で切って … を付けたい」場面はよくあります。素の CSS では複数行の省略は書くのが面倒ですが、Tailwind では line-clamp-* 一発です(v4 でコア機能です)。
<p class="line-clamp-3">長い説明文をここに……(3 行を超えたら省略記号で切られる)</p>
line-clamp-none で解除できます。一覧画面のカードで高さを揃えるのに重宝します。
11.5 リッチテキスト全体を整える @tailwindcss/typography
ここまでは「自分でクラスを付けられる要素」の話でした。しかし、CMS やマークダウンが吐き出す本文 HTML には、自分でクラスを付けられません。<h2> や <p> や <ul> がクラスなしで流れてきます。第3章で「Tailwind が向かない場面」として挙げたケースです。
これを解決するのが公式プラグイン @tailwindcss/typography です。本文を囲む要素に prose クラスを 1 つ付けるだけで、中の見出し・段落・リスト・引用・コードなどが、まとめて読みやすく整形されます。
<article class="prose">
<!-- ここに CMS が出力した HTML が入る -->
</article>
prose-lg で全体を大きく、dark:prose-invert でダークモード対応もできます。ブログ記事やドキュメントページの定番です。プラグインの導入方法(v4 では CSS で @plugin "@tailwindcss/typography";)は第26章で扱います。
11.6 実務: 本文・記事ページの型
実務では、文字まわりは「型」を決めて使い回すのが効率的です。たとえば「記事本文は prose prose-lg、見出しは text-3xl font-bold tracking-tight、補足テキストは text-sm text-gray-500」のように、用途ごとの組み合わせをコンポーネント(第6部)に畳み込みます。任意のフォントサイズ(text-[15px] など)に手を出す前に、まずスケールの中で表現できないかを考えるのが、一貫性を守るコツです。
参考資料
- Tailwind CSS Docs — Font size
- Tailwind CSS Docs — Font family / weight
- Tailwind CSS Docs — Typography plugin
第12章 Colors
12.1 デフォルトパレットと oklch / P3
色は、Tailwind が「制約のあるデザイン」を最もよく体現する領域です。素の CSS では 1,600 万色から自由に選べますが、それが第3章で見た「微妙に違う青が散らばる」問題を生みます。
Tailwind は、色を 名前 + 明度の段階(red-500・blue-600 など)のパレットとして提供します。各色に 50(最も明るい)から 950(最も暗い)までの段階があります。そして第5章で見たとおり、v4 のパレットは oklch で定義されています。
--color-red-500: oklch(63.7% 0.237 25.331);
oklch は「見た目の明るさ」に沿った色空間なので、500 → 600 → 700 と数字を上げると素直に暗くなり、配色を組みやすいという利点があります。実務では「oklch という新しい書き方になった」程度の認識で十分ですが、自分で色を足すときも oklch で書くと段階を揃えやすくなります。
12.2 背景・文字・ボーダー・リング色
同じパレットを、適用先ごとに別のクラスで使います。プレフィックスが変わるだけです。
<div class="bg-blue-600 text-white border border-blue-700">
ボタン
</div>
bg-*… 背景色text-*… 文字色border-*… ボーダー色ring-*… リング(フォーカス枠など、第13章)fill-*/stroke-*… SVG の塗り / 線
12.3 不透明度の指定
色に透明度を加えたいとき、v4 では色クラスの後ろに / と数値を付けます。
<div class="bg-black/50">黒の 50% 透過</div>
<div class="text-blue-600/75">青の 75%</div>
この bg-black/50 は、内部的には CSS の color-mix()(第2部で触れたモダン CSS 機能)を使って不透明度を合成しています。rgba を手で書く必要がなく、パレットの色をそのまま透かせるのが便利です。
12.4 グラデーション
v4 ではグラデーションの表現が大きく広がりました。方向を決める bg-linear-to-r(右へ向かう線形グラデーション)などに、開始・中間・終了の色を重ねます。
<div class="bg-linear-to-r from-cyan-500 to-blue-500">線形グラデーション</div>
線形だけでなく、放射状(基本形は bg-radial、位置を指定するなら bg-radial-[at_25%_25%] のように書きます)や円錐(基本形は bg-conic、角度を付けるなら bg-conic-<角度>)も使えます。装飾過多にならない範囲で、ヒーロー領域やボタンのアクセントに使うとよいでしょう。
12.5 テーマでの色のカスタムと意味的な色名
ブランドカラーは、第5章のとおり @theme で定義します。このとき、--color-brand-blue のような「見た目の名前」だけでなく、役割を表す意味的な名前(セマンティックトークン)も定義しておくと、後が楽になります。
@theme {
--color-brand: oklch(0.45 0.24 264); /* ブランド色 */
--color-surface: oklch(1 0 0); /* 背景面 */
--color-danger: oklch(0.58 0.22 27); /* 危険・エラー */
}
こうすると bg-surface・text-danger のように「意味」で色を呼べます。「エラーは赤」という決定を 1 か所に集約でき、後でブランド色を変えるときも定義の差し替えだけで済みます。これはダークモード(第18章)やデザインシステム(第23章)への布石にもなります。
12.6 実務: ブランドカラーの組み込みとダークモード前提の設計
実務では、最初からダークモードを前提に色を設計しておくと後悔が減ります。具体的には、bg-white・text-black のような直接的な色ではなく、12.5 のセマンティックトークン(bg-surface・text-foreground など)で組んでおき、ダークモード時にトークンの値だけを差し替える、という設計です(第18章)。text-[#1a1a1a] のような任意の色を散らすと、ダークモード対応や配色変更のときに総とっかえになります。色こそ、任意の値を避けてテーマに寄せるべき領域です。
参考資料
第13章 Borders と Effects
13.1 ボーダー幅・色・スタイル
境界線は、要素の区切りを示す基本的な装飾です。Tailwind では幅・色・スタイルを別々のユーティリティで指定します。
<div class="border border-gray-200">細い枠(既定 1px)</div>
<div class="border-2 border-dashed border-blue-500">破線の太枠</div>
- 幅:
border(1px)・border-2・border-4、方向別にborder-t・border-xなど - 色:
border-*(パレット。第12章) - スタイル:
border-solid・border-dashed・border-dotted
v4 の注意: v3 では
borderだけ書くと色が既定でグレーになっていましたが、v4 では既定のボーダー色がcurrentColor(その要素の文字色)に変わりました。意図した色にするにはborder-gray-200のように色を明示します。
13.2 角丸
rounded-* で角を丸めます。値は --radius-* テーマから来ています。
<button class="rounded-lg">やや丸い</button>
<img class="rounded-full" /> <!-- 真円・ピル形 -->
rounded-sm < rounded-md < rounded-lg < rounded-xl と大きくなり、rounded-full で完全な丸になります。一部の角だけ丸めたいときは rounded-t-lg(上だけ)のように方向を付けます。
13.3 影とリング
影(shadow-*)は、要素を浮き上がって見せます。v4 のスケールは小さい順に shadow-2xs・shadow-xs・shadow-sm・shadow-md・shadow-lg・shadow-xl・shadow-2xl、無しは shadow-none です。
<div class="shadow-md">少し浮いたカード</div>
v4 の重要な注意: 影のスケール名が v3 からずれました。とくに気をつけたいのは、v3 の
shadow-smが v4 ではshadow-xs相当に、v3 のshadow(無印)が v4 ではshadow-sm相当になった点です。つまり古い記事でshadow-smと書かれていたら、v4 ではshadow-xsに読み替える必要があります。無印のshadowも互換のため残っていますが、本書(v4)では混乱を避けるため、つねにshadow-smのように明示的な段階名で書きます。
リング(ring-*)は、要素の周りに「輪」を描きます。box-shadow を使うため、レイアウトを押し広げずに枠を足せるのが特徴で、主にフォーカスの可視化に使います。
<button class="focus:ring-2 focus:ring-blue-500">フォーカスで青い輪</button>
v4 では内側に影や輪を入れる inset-shadow-*・inset-ring-* も加わりました。
13.4 フィルタなどの効果
要素にぼかしや明度補正などの視覚効果をかけられます。素の CSS の filter に対応します。
<img class="blur-sm brightness-110" />
<div class="opacity-50">半透明</div>
blur-*… ぼかしbrightness-*/contrast-*… 明度・コントラストopacity-*… 不透明度(要素全体)backdrop-blur-*… 背景側をぼかす(すりガラス効果)
13.5 背景画像・サイズ・位置
背景画像まわりは、bg-cover(領域を覆う)・bg-center(中央寄せ)・bg-no-repeat などで制御します。画像 URL 自体は任意の値かインラインスタイルで渡すのが一般的です。
<div class="bg-cover bg-center" style="background-image: url(/hero.jpg)">...</div>
13.6 実務: 立体感とフォーカスリング
実務でのコツは 2 つです。1 つは影を盛りすぎないこと。shadow-sm〜shadow-md を基準にし、shadow-2xl は本当に浮かせたい要素だけに使うと、画面が落ち着きます。もう 1 つは、フォーカスリングを消さないこと。デザイン上の理由で outline-none にする場合は、必ず focus:ring-2 などで代わりの可視化を用意します。これはアクセシビリティの必須事項です(第21章)。
参考資料
- Tailwind CSS Docs — Border width / color
- Tailwind CSS Docs — Box shadow
- Tailwind CSS Docs — Filter(filter プロパティ本体)
- Tailwind CSS Docs — blur(
blur-*) - Tailwind CSS Docs — brightness(
brightness-*) - Tailwind CSS Docs — backdrop-blur(
backdrop-blur-*)
第14章 Layout
14.1 display
レイアウトの出発点は display です。要素を「どう積むか」を決めます。
<span class="block">ブロックにする</span>
<div class="inline-block">並べられるブロック</div>
<div class="hidden md:block">狭い画面では消す</div>
block/inline/inline-blockflex/grid… 次章以降の主役hidden…display: none(要素を消す)
hidden md:block のように、レスポンシブと組み合わせて「特定の画面幅だけ表示」を作るのは頻出パターンです(第17章)。
14.2 position と重なり
position で要素の配置基準を決め、top/right/bottom/left(まとめて inset-*)でずらします。重なり順は z-* です。
<div class="relative">
<span class="absolute top-2 right-2">バッジ</span>
</div>
relative を付けた親を基準に、absolute の子を四隅へ配置する——この組み合わせが、バッジや閉じるボタンの定番です。sticky top-0 でスクロール追従ヘッダーも作れます。
14.3 サイズ(width / height / size)
幅・高さの指定です。w-* / h-* は第10章の --spacing スケールを共有します。さらに割合・画面基準の値も使えます。
<div class="w-64">幅 = spacing 64</div>
<div class="w-1/2">親の 50%</div>
<div class="w-full">親いっぱい</div>
<div class="h-dvh">画面の高さ(動的ビューポート)</div>
- 数値:
w-64(calc(var(--spacing) * 64)) - 分数:
w-1/2・w-1/3(親に対する割合) - 特殊:
w-full(100%)・w-screen(100vw)・w-dvw/h-dvh(モバイルのアドレスバーを考慮した動的ビューポート) - 制限:
min-w-*・max-w-*(最小・最大)
v4 で便利なのが size-* です。幅と高さを同時に設定します。アイコンのように正方形にしたい要素で重宝します。
<svg class="size-6">...</svg> <!-- w-6 h-6 と同じ -->
14.4 overflow と object
はみ出しの扱いは overflow-* です。overflow-hidden(隠す)・overflow-auto(必要なときスクロール)・overflow-x-auto(横だけ)など。画像をボックスに収める比率は object-cover(覆う)・object-contain(収める)で制御します。
<img class="h-48 w-full object-cover" /> <!-- 領域に合わせて切り抜く -->
14.5 コンテナと Container Queries
「中身を読みやすい幅で中央に収めたい」ときは mx-auto max-w-* の組み合わせが基本です。
<div class="mx-auto max-w-3xl px-4">読みやすい幅の本文</div>
そして v4 の目玉が Container Queries(コンテナクエリ)です。第17章で扱うレスポンシブは「画面幅」に反応しますが、コンテナクエリは「親要素の幅」に反応します。サイドバーの中でもメイン領域の中でも、置かれた場所の幅に応じてレイアウトを変えられます。
<div class="@container">
<div class="flex flex-col @md:flex-row">
<!-- 親(@container)が md 幅以上になったら横並びに -->
</div>
</div>
親に @container を付け、子に @md: のような接頭辞を付けます。これにより、コンポーネントが「どの画面か」ではなく「自分が今どれだけの幅を与えられているか」で自律的にレイアウトを決められます。再利用しやすいコンポーネントを作るうえで強力な機能です。
14.6 実務: ページの骨格を組む手順
実務では、いきなり細部を作らず、外側から内側へ組むと崩れにくくなります。おすすめの順序は、(1) ページ全体の最大幅と中央寄せ(mx-auto max-w-*)→ (2) 大きな領域分け(Flex / Grid、第15章・第16章)→ (3) 各領域の余白(第10章)→ (4) 中身の装飾、です。骨格が決まる前から absolute や任意のサイズで微調整を始めると、後で破綻しがちです。
参考資料
- Tailwind CSS Docs — Display
- Tailwind CSS Docs — Width / Size
- Tailwind CSS Docs — Responsive design(Container queries)
第15章 Flexbox
15.1 Flexbox の素の CSS と Tailwind の対応
Flexbox は「要素を 1 方向(横一列か縦一列)に並べ、余白や伸縮を制御する」レイアウト手法です。読者はすでに Flexbox の基礎を学んでいる前提なので、ここでは素の CSS と Tailwind の対応を押さえることに集中します。Tailwind のクラスは、CSS プロパティをほぼそのまま短くしただけです。
| 素の CSS | Tailwind |
|---|---|
display: flex | flex |
flex-direction: column | flex-col |
justify-content: center | justify-center |
align-items: center | items-center |
gap: 1rem | gap-4 |
つまり Flexbox を知っていれば、Tailwind の Flex は「短い別名を覚えるだけ」です。
15.2 方向・折り返し
<div class="flex flex-row">横並び(既定)</div>
<div class="flex flex-col">縦並び</div>
<div class="flex flex-wrap">入りきらなければ折り返す</div>
flex だけだと横並び(flex-row)になります。縦に積みたいときは flex-col、子が多くて折り返したいときは flex-wrap を足します。
15.3 主軸・交差軸の配置
Flexbox の肝は「2 つの軸での配置」です。混同しやすいので整理します。
- 主軸方向の配置 =
justify-*:justify-start/justify-center/justify-between(両端に寄せて間を均等に)/justify-end - 交差軸方向の配置 =
items-*:items-start/items-center/items-stretch
<!-- 横並びで、左右両端に配置し、縦は中央そろえ -->
<div class="flex justify-between items-center">
<span>ロゴ</span>
<nav>メニュー</nav>
</div>
この justify-between items-center は、ヘッダーの「ロゴを左、メニューを右、縦は中央」という最頻出パターンです。
15.4 伸縮(grow / shrink / basis)
子要素が空きスペースをどう分け合うかを制御します。
grow… 余ったスペースを埋めるように伸びるshrink-0… 縮まない(アイコンなどを潰したくないとき)basis-*… 基準サイズ
<div class="flex">
<input class="grow" /> <!-- 入力欄が伸びる -->
<button class="shrink-0">検索</button> <!-- ボタンは潰れない -->
</div>
15.5 個別配置(self / order)
特定の子だけ別扱いにしたいときは self-*(その子の交差軸配置)や order-*(並び順の変更)を使います。order-first / order-last で、HTML の順序を変えずに見た目の順序だけ入れ替えられます。
15.6 実務: ナビゲーションバー・ツールバー
Flexbox の実務での主役は、横並びの UI です。ナビゲーションバー、ツールバー、ボタンの並び、アイコン+ラベルなど。基本の型は flex items-center gap-* で、これに justify-between(両端寄せ)や grow(一部を伸ばす)を足して組み立てます。1 次元(一列)の配置なら Flexbox、2 次元(行と列の格子)なら次章の Grid、という使い分けを覚えておきましょう。
参考資料
第16章 Grid
16.1 Grid の素の CSS と Tailwind の対応
CSS Grid は「行と列の格子で 2 次元にレイアウトする」手法です。Flexbox が 1 方向の並びを得意とするのに対し、Grid は「縦も横も揃えたい」表組み的なレイアウトに向きます。こちらも対応関係を押さえましょう。
| 素の CSS | Tailwind |
|---|---|
display: grid | grid |
grid-template-columns: repeat(3, minmax(0,1fr)) | grid-cols-3 |
gap: 1rem | gap-4 |
grid-column: span 2 | col-span-2 |
16.2 列・行の定義
grid-cols-* で列数を決めます。grid-cols-3 は「等幅 3 列」です。
<div class="grid grid-cols-3 gap-4">
<div>1</div><div>2</div><div>3</div>
</div>
このとき生成される CSS はこうです。
.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
minmax(0, 1fr) になっているのは、中身が大きくても列がはみ出さないようにするための定番テクニックで、Tailwind が標準で面倒を見てくれています。複雑なトラックは任意の値で書けます(例: grid-cols-[200px_1fr])。
16.3 配置とスパン
子要素を「何列ぶん占めるか」「何列目から始めるか」で配置します。
<div class="grid grid-cols-4 gap-4">
<div class="col-span-2">2 列ぶん</div>
<div>1 列</div>
<div>1 列</div>
</div>
col-span-*… 列方向にまたぐcol-start-*/col-end-*… 開始・終了位置- 行方向は
row-span-*row-start-*
16.4 gap と Flexbox との使い分け
Grid でも間隔は gap-* で空けます(第10章)。gap-x-* / gap-y-* で縦横を別々にもできます。
「Flexbox と Grid のどちらを使うか」は実務でよく迷うところですが、目安はシンプルです。一列(または一行)に並べるだけなら Flexbox、行と列の両方をきっちり揃えたいなら Grid です。ボタンの横並びは Flex、商品カードの格子は Grid、と考えれば外しません。
16.5 動的な列数
第2部で見たとおり、v4 では grid-cols-15 のようなスケールに用意されていない列数も、設定なしで動的に生成されます。
<div class="grid grid-cols-7">7 列のカレンダー</div>
これは「定義していないクラスがなぜ効くのか」(第4章)の好例です。とはいえ、極端な列数を任意の値で乱発すると可読性が落ちるので、よく使う列数はレスポンシブの型(次項)に落とし込むのが実務的です。
16.6 実務: カードグリッド・ダッシュボード
Grid の実務での主役は、カード一覧とダッシュボードです。カード一覧は、画面幅に応じて列数を増やすレスポンシブグリッドが定番です。
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
<!-- カードを並べる -->
</div>
これは「スマホで 1 列、タブレットで 2 列、PC で 3 列」という、最もよく書くパターンです(レスポンシブは第17章で深掘りします)。ダッシュボードのように「大きいパネルと小さいパネルを混在させる」レイアウトは、col-span-* / row-span-* を組み合わせて表現します。
第4部はここまでです。余白・文字・色・装飾・レイアウト・Flex・Grid という、画面を組む道具がそろいました。いずれも第2部の仕組み(--spacing・oklch・任意の値・container query)の上に乗っていることを思い出してください。次の第5部では、これらを使ってレスポンシブ・ダークモード・アニメーション・フォーム・アクセシビリティという実践的なテーマに踏み込みます。
参考資料
- Tailwind CSS Docs — Grid template columns
- Tailwind CSS Docs — Grid column(span / start / end)
- Tailwind CSS Docs — Gap
演習(第4部)
ユーティリティは「組み合わせて作る」のが本質です。Tailwind Play で実際に組み立てましょう。
- カードを作ろう: 白背景・角丸・影・内側余白を持つカードに、太字の見出しとグレーの本文を入れてください(第10章〜第13章)。使ったクラスをすべて挙げ、それぞれが何を担当しているか説明できますか。
- レスポンシブグリッド: 商品カードを「スマホで 1 列、タブレットで 2 列、PC で 3 列」に並ぶグリッドにしてください(§16.6)。
- 任意の値を疑う:
p-[17px]と書きたくなる場面を 1 つ想像し、それを spacing スケール(p-4/p-5など)で代替できないか、できないならテーマ変数にすべきか、を判断してください(§10.5・§10.6)。
第5部 実践的な Tailwind CSS
第4部までで、画面を組むための道具がそろいました。この第5部では、それらを使って実際のプロダクトで必ず求められるテーマに取り組みます。レスポンシブ(第17章)、ダークモード(第18章)、アニメーション(第19章)、フォーム(第20章)、アクセシビリティ(第21章)です。
これらはすべて、第2部で学んだバリアントの仕組み(第6章)の応用です。md:・dark:・hover:・motion-reduce:・invalid: といったバリアントが、ここで実戦投入されます。「条件付きでスタイルを当てる」という Tailwind の核心が、最も生きる場面です。
第17章 レスポンシブデザイン
17.1 モバイルファースト原則
レスポンシブデザインとは、画面幅に応じてレイアウトを変えることです。素の CSS ではメディアクエリ(@media)で書きますが、Tailwind では第6章で見たブレークポイントのバリアント(sm: md: lg: …)を使います。
ここで最も重要な原則がモバイルファーストです。Tailwind では、プレフィックスなしのクラスはすべての画面に効き、md: などのプレフィックス付きは「その幅以上」で上書きするという設計になっています。
<!-- スマホでは縦並び、md 以上で横並び -->
<div class="flex flex-col md:flex-row">...</div>
ここでよくある誤解を解いておきます。md: は「md サイズのときだけ」ではなく「md 幅以上のすべて」を意味します。だから設計の順序は「まず狭い画面(無印)を作り、広くなったら上書きする」になります。sm:text-left を「スマホのとき左寄せ」と誤解すると、意図と逆になります。無印=土台、プレフィックス=広い画面での変更、と覚えてください。
17.2 デフォルトブレークポイントとカスタム
v4 の既定ブレークポイントは次のとおりです(第6章で確認した値です)。
| プレフィックス | 最小幅 | 生成される CSS |
|---|---|---|
sm: | 40rem (640px) | @media (width >= 40rem) |
md: | 48rem (768px) | @media (width >= 48rem) |
lg: | 64rem (1024px) | @media (width >= 64rem) |
xl: | 80rem (1280px) | @media (width >= 80rem) |
2xl: | 96rem (1536px) | @media (width >= 96rem) |
カスタムしたいときは、第5章のとおり @theme で --breakpoint-* を定義します。たとえば --breakpoint-3xl: 120rem; と書けば 3xl: が使えるようになります。
17.3 範囲指定・max-* バリアント
「ある幅以上」だけでなく「ある幅未満」を指定したいこともあります。そのときは max-* バリアントを使います。
<div class="max-md:hidden">md 未満では隠す</div>
md: と max-* を組み合わせると、「md 以上 xl 未満だけ」のような範囲指定もできます。
<div class="md:max-xl:flex">md 以上 xl 未満のときだけ flex</div>
ただし範囲指定は読みにくくなりがちなので、本当に必要なときに限るのが無難です。
17.4 メディアクエリ vs Container Queries
ここが現代のレスポンシブで最も大切な使い分けです。第14章で触れたContainer Queries(コンテナクエリ)との違いを整理します。
- ブレークポイント(
md:など)= 画面の幅に反応する。 ページ全体のレイアウトを切り替えるのに向く。 - コンテナクエリ(
@md:など)= 親要素の幅に反応する。 「どこに置かれるか分からないコンポーネント」を、置かれた場所の幅に応じて変えるのに向く。
<!-- このカードは、画面ではなく自分の親の幅で形を変える -->
<div class="@container">
<article class="flex flex-col @md:flex-row">...</article>
</div>
同じカードを、広いメイン領域に置いても狭いサイドバーに置いても、それぞれの幅に合わせて自律的に振る舞えます。ページ全体の構成はブレークポイント、再利用するコンポーネントはコンテナクエリ、という使い分けを覚えておくと、設計がぐっと洗練されます。
17.5 実務: ブレークポイント設計のアンチパターン
実務で最もありがちな失敗は、ブレークポイントを刻みすぎることです。sm: md: lg: xl: 2xl: すべてに別々の値を指定したクラスが並ぶと、もはや誰も挙動を追えません。
<!-- アンチパターン: 分岐が多すぎる -->
<div class="text-sm sm:text-base md:text-lg lg:text-xl xl:text-2xl">...</div>
多くの場合、切り替えは 1〜2 段階で十分です。「スマホと PC の 2 パターン」を基本に考え、どうしても必要なときだけ中間を足す。これだけで、レスポンシブはずっと保守しやすくなります。
参考資料
第18章 ダークモード
18.1 dark: バリアントの仕組み
ダークモードは、dark: バリアントで実現します。第6章で見たとおり、これは「ダークモードのときだけ効くスタイル」を生成するバリアントです。
<div class="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
ライトでは白地・黒字、ダークでは黒地・白字
</div>
無印(bg-white text-gray-900)がライトモードの見た目、dark: 付きがダークモードでの上書きです。第17章のモバイルファーストと同じ「土台+上書き」の構造です。
18.2 media(OS 連動)と class/selector(手動切替)戦略
ダークモードの「効くタイミング」には、大きく 2 つの戦略があります。
- OS 連動(既定): 利用者の OS 設定(
prefers-color-scheme)に従って自動で切り替わります。設定不要で、dark:がそのまま OS のダークモードに反応します。 - 手動切替(クラス方式): ページ内のトグルボタンで切り替えたい場合。
<html>などに.darkクラスが付いているときだけdark:を効かせます。
「ユーザーに切り替えボタンを提供したい」なら、後者の手動切替を選びます。
18.3 v4 でのダークモード設定(@custom-variant dark)
手動切替にするには、第6章で触れた @custom-variant を使って dark: の意味を上書きします。v4 では CSS にこう書きます。
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
これで dark: は「OS 設定」ではなく「祖先に .dark クラスがあるとき」に効くようになります。あとは JavaScript で <html> に .dark を付け外しすれば、テーマが切り替わります。
// ダークにする / 戻す
document.documentElement.classList.toggle('dark')
v3 を知っている人へ: v3 では
tailwind.config.jsにdarkMode: 'class'と書きました。v4 ではこの設定が CSS の@custom-variantに移りました。
18.4 トグル実装とちらつき対策
手動切替で必ずぶつかるのが、初期表示のちらつき(FOUC: Flash of Unstyled Content)です。ページ読み込み時、JavaScript で .dark を付ける前に一瞬ライトモードが表示され、直後に暗転する——この不快なちらつきです。
原因は、「.dark を付ける JavaScript が、画面の描画より後に走る」ことです。対策は、描画前(<head> の早い段階)に、同期的なスクリプトで .dark を決定することです。
<!-- <head> の早い位置に置く同期スクリプト -->
<script>
if (localStorage.theme === 'dark' ||
(!('theme' in localStorage) &&
window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark')
}
</script>
保存済みの設定(localStorage)か OS 設定を見て、最初の描画より前にクラスを付けます。Rails でも React/Next.js でも、考え方は同じです。Rails ならレイアウトの <head> にこのスクリプトを直接書きます。Next.js(App Router)では、同様のスクリプトを <head> に同期実行で差し込みます。これは、テーマをブラウザの localStorage に保存する場合の定石です。
なお、テーマを Cookie やデータベースに保存する設計であれば、サーバー側でユーザーの設定を知れるので、最初から <html class="dark"> をサーバーレンダリングして出力できます。この場合は描画前のスクリプトが不要になり、ちらつきも原理的に起きません(公式ドキュメントもこの方法に触れています)。「クライアント保存ならスクリプト、サーバー保存なら初期クラスを出力」と使い分けると覚えてください。
18.5 実務: 色設計をダークモード前提にする
ダークモード対応で苦労しないコツは、第12章でも触れたとおり、最初からセマンティックトークンで色を組むことです。
dark: を要素ごとにベタ書きすると、bg-white dark:bg-gray-900 のような対が画面中に散らばり、後で色を調整するのが地獄になります。代わりに、第5章のテーマ変数を使い、.dark のときに変数の値だけを差し替える設計にします。
@theme {
--color-surface: oklch(1 0 0); /* ライトの背景 */
--color-foreground: oklch(0.2 0 0); /* ライトの文字 */
}
.dark {
--color-surface: oklch(0.2 0 0); /* ダークでは反転 */
--color-foreground: oklch(0.95 0 0);
}
こうしておけば、HTML は bg-surface text-foreground と書くだけで両モードに対応します。dark: を個別に書く量が激減し、配色の調整も CSS 変数の 1 か所で済みます。
参考資料
第19章 アニメーション
19.1 transition
アニメーションの基本はトランジション——状態が変わるときに、見た目をなめらかに変化させることです。hover: などで色やサイズが変わるとき、transition を付けると瞬間的にではなく徐々に変化します。
<button class="bg-blue-500 transition hover:bg-blue-700 duration-200 ease-out">
ホバーでなめらかに色が変わる
</button>
transition… トランジションを有効にするduration-200… 変化にかける時間(200ms)ease-out… 変化の緩急(イージング)delay-*… 開始を遅らせる
19.2 transform
トランスフォームは、要素を移動・拡大・回転させます。レイアウトに影響を与えずに見た目を動かせるので、アニメーションと相性が良い機能です。
<div class="transition hover:scale-105 hover:-translate-y-1">
ホバーで少し拡大して浮き上がる
</div>
translate-x-*/translate-y-*… 移動scale-*… 拡大・縮小rotate-*… 回転
v4 では、これらが個別の CSS プロパティ(translate・scale・rotate)として扱われるようになり、3D の変形(rotate-x-* など)も扱えます。
19.3 組み込みアニメーションとカスタム
繰り返し動き続けるアニメーションには、組み込みのユーティリティがあります。
<svg class="animate-spin">...</svg> <!-- 回転(ローディング) -->
<div class="animate-pulse">...</div> <!-- 点滅(スケルトン表示) -->
animate-spin… 回転し続ける(ローディングスピナー)animate-pulse… ゆっくり点滅(読み込み中のプレースホルダー)animate-bounce… 跳ねる
独自のアニメーションは、第5章のテーマで --animate-* を定義して追加します。@theme に @keyframes とあわせて定義することで、animate-自分の名前 が使えるようになります。
19.4 入場アニメーション @starting-style
「要素が現れる瞬間」をアニメーションさせたい場面——モーダルやポップオーバーがふわっと出てくる演出——は、従来 JavaScript が必要でした。v4 は、モダン CSS の @starting-style に対応する starting: バリアントを用意しており、これを CSS だけで書けます。
starting: は「要素が DOM に最初に現れた瞬間(または display: none から表示に変わった瞬間)の、開始時点の見た目」を指定します。
<div popover id="menu"
class="opacity-100 transition-opacity starting:opacity-0">
ふわっと現れるメニュー
</div>
「開始時は透明(starting:opacity-0)→ 表示後は不透明(opacity-100)」へ、transition でなめらかにつなぎます。JavaScript なしで入場アニメーションが書けるのは、v4 がモダン CSS の薄いラッパーであることの好例です。
19.5 motion-reduce への配慮
アニメーションは、すべての人にとって快適とは限りません。前庭障害などのある人にとって、動きの多い画面は不快や体調不良の原因になります。OS には「視差効果を減らす(reduce motion)」設定があり、これを尊重するのがアクセシビリティの基本です。
Tailwind では motion-reduce: バリアントで対応します。
<div class="animate-bounce motion-reduce:animate-none">
通常は跳ねるが、reduce motion 設定なら止まる
</div>
motion-reduce:animate-none で、reduce motion を有効にしているユーザーには動きを止めます。逆に motion-safe:(動きを許可している人にだけ animate を付ける)という書き方もあります。装飾的なアニメーションには、原則この配慮をセットにする習慣をつけましょう(第21章)。
19.6 実務: マイクロインタラクションと過剰演出の回避
実務でのアニメーションは、控えめなマイクロインタラクション(ボタンのホバー、フォーカス時のリング、読み込み中の表示など)に効果を発揮します。ユーザーに「反応した」という手応えを返すのが目的です。
逆に避けたいのは、過剰な演出です。スクロールのたびに要素が派手に動く、画面遷移ごとに大きなアニメーションが入る、といった演出は、最初は楽しくてもすぐに鬱陶しくなり、操作の邪魔になります。「気づかないくらい自然」が良いアニメーションの目安です。duration-200 前後の短いトランジションを基本に、motion-reduce への配慮を忘れずに、が実務の定石です。
参考資料
- Tailwind CSS Docs — Transition property
- Tailwind CSS Docs — Translate
- Tailwind CSS Docs — Scale
- Tailwind CSS Docs — Rotate
- Tailwind CSS Docs — Animation
- Tailwind CSS Docs — Hover, focus & other states(starting / motion-reduce)
第20章 フォームデザイン
20.1 素のフォーム要素のばらつきと @tailwindcss/forms
フォーム要素(<input>・<select>・チェックボックスなど)は、ブラウザや OS によって見た目がバラバラで、しかも素の状態ではユーティリティで整えにくいという厄介な性質があります。チェックボックスの色を変えるだけでも、本来は手間がかかります。
これを解決するのが公式プラグイン @tailwindcss/forms です。このプラグインは、フォーム要素にユーティリティで上書きしやすい素直なリセット(土台のスタイル)を当てます。これを入れておくと、<input> や <select> を、ふつうのユーティリティ(rounded・border・focus:ring-2 など)でそのまま整えられるようになります。
既定では、このプラグインは 2 つを同時に提供します。1 つはフォーム要素すべてにグローバルに当たるリセット、もう 1 つは form-input・form-select・form-multiselect・form-checkbox・form-radio・form-textarea というクラス群です。何も設定しなければ両方が使えます。
そのうえで、片方だけに制限したいときに strategy を指定します。
strategy: "base": グローバルなリセットだけにする(クラスは生成しない)。多くの新規プロジェクトはこれで十分です。strategy: "class":form-*クラスを付けた要素にだけリセットを当てる(グローバルには当てない)。既存の見た目を壊したくないとき向け。
導入(v4 では CSS に @plugin "@tailwindcss/forms";)は第26章で扱います。
20.2 入力・選択・チェック/ラジオのスタイリング
@tailwindcss/forms を入れた前提で、フォーム要素はふつうのユーティリティで整えます。
<input type="text"
class="rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-500" />
<input type="checkbox" class="rounded text-blue-600 focus:ring-blue-500" />
チェックボックスやラジオの色は text-*(チェック時の色)で指定できるようになります。これがプラグインの効果です。
20.3 状態表現(focus / invalid / disabled / peer)
フォームでは「状態を見た目に反映する」ことが重要です。第6章のバリアントが主役になります。
<input required
class="border-gray-300
focus:ring-2 focus:ring-blue-500
invalid:border-red-500
disabled:bg-gray-100 disabled:cursor-not-allowed" />
focus:… 入力中の強調invalid:… 入力値が不正なとき(required未入力やメール形式違反など)required:… 必須項目disabled:… 無効化されているとき
さらに第6章で見た peer-* を使うと、入力欄の状態に応じてエラーメッセージを出すといった連動を、JavaScript なしで書けます。
<input type="email" class="peer ..." />
<p class="hidden peer-invalid:block text-sm text-red-600">
メールアドレスの形式が正しくありません
</p>
20.4 field-sizing など新ユーティリティ
v4 には、フォームで便利な新ユーティリティもあります。たとえば field-sizing-content は、テキストエリアなどを中身の量に応じて自動でリサイズさせます。入力が増えると勝手に広がるテキストエリアが、JavaScript なしで作れます。
<textarea class="field-sizing-content" placeholder="入力すると自動で広がります"></textarea>
20.5 実務: Rails / React フォームでの組み込みと「過信しない」範囲
実務では、フォームは各フレームワークのヘルパーが生成する要素に、Tailwind のクラスを与える形になります。
Rails の form_with なら、ヘルパーに class: を渡します。
<%= form_with model: @user do |f| %>
<%= f.email_field :email,
class: "rounded-md border-gray-300 focus:ring-2 focus:ring-blue-500" %>
<% end %>
React なら、入力要素に className を付けるだけです(第25章で再利用の工夫を扱います)。
ここで大切な注意があります。@tailwindcss/forms と Tailwind が面倒を見るのは、あくまで見た目だけです。
- 入力の検証(バリデーション): 値が正しいかの確認は、Rails のモデルバリデーションやフロントエンドのバリデーションが担います。Tailwind は
invalid:で「不正なときの見た目」を出せるだけで、何が不正かを判断するのは別の層の仕事です。 - アクセシビリティ: ラベルと入力の関連付け(
<label for>)、エラーの読み上げ(aria-describedbyなど)は、HTML を正しく書く必要があります(第21章)。@tailwindcss/formsはこれらを自動でやってはくれません。
「フォームの見た目は Tailwind、正しさとアクセシビリティは HTML とアプリ側」という役割分担を、過信せずに押さえておきましょう。
参考資料
- @tailwindcss/forms(GitHub)
- Tailwind CSS Docs — Field sizing(
field-sizing-content/field-sizing-fixed) - Tailwind CSS Docs — Hover, focus & other states(invalid / required / peer)
第21章 アクセシビリティ
21.1 ユーティリティと a11y の関係
アクセシビリティ(a11y)は、障害の有無や利用環境にかかわらず、誰もが使える Web を作るための取り組みです。ここで最初に押さえるべき大原則があります。
クラスは「見た目」、意味は「HTML」が担う。
Tailwind のユーティリティは見た目を整えるだけで、要素の意味は HTML が決めます。<div class="..."> をボタンのように装飾しても、スクリーンリーダーにはボタンとして伝わりません。ボタンは <button>、見出しは <h1>〜<h6>、ナビゲーションは <nav> を使う——この「正しい HTML を書く」ことが、アクセシビリティの土台です。Tailwind を使っても、この原則は何も変わりません。むしろ「クラスで見た目を作れてしまう」からこそ、正しい要素を選ぶ意識が大切です。
21.2 視覚的に隠す sr-only
「目には見せないが、スクリーンリーダーには読ませたい」テキストがあります。たとえばアイコンだけのボタンには、見た目上はラベルが要りませんが、スクリーンリーダー利用者には何のボタンか伝える必要があります。
このための専用ユーティリティが sr-only です。
<button>
<svg><!-- 検索アイコン --></svg>
<span class="sr-only">検索</span>
</button>
sr-only は、要素を視覚的に消しつつ(display: none とは違い)支援技術には読み上げさせる、という CSS を当てます(実体は position: absolute; width: 1px; height: 1px; overflow: hidden; ... といった定番のテクニックです)。display: none だと読み上げからも消えてしまうので、この使い分けが重要です。逆に「狭い画面では隠し、広い画面では見せる」には sr-only sm:not-sr-only のように not-sr-only で打ち消します。
21.3 フォーカス可視化(focus-visible とリング)
キーボードだけで操作する人にとって、「いまどこにフォーカスがあるか」が見えることは死活問題です。ところがデザインの都合で、ブラウザ標準のフォーカス枠を outline-none で消してしまう例が後を絶ちません。フォーカスの可視化を消すなら、必ず代わりを用意するのが鉄則です。
ここで役立つのが第6章の focus-visible: です。これは CSS の :focus-visible に対応するもので、ブラウザが「フォーカス表示を出すべきだ」と判断したときだけスタイルを当てます。ブラウザは入力方式(マウスかキーボードか)や要素の種類から自動でこれを判断します。たとえばボタンの場合、キーボード操作でフォーカスしたときには枠が出て、マウスでクリックしただけのときには出ない、という具合です。これにより、マウス利用者には余計な枠を出さず、キーボード利用者にはしっかり枠を見せられます。
ただし「マウスでは絶対に出ない」と言い切れるわけではありません。テキスト入力欄のように、クリックでフォーカスしても表示が必要とブラウザが判断する要素では、マウス操作でも枠が出ることがあります。挙動はブラウザと要素任せで、こちらが細かく制御するものではない、と理解しておくとよいでしょう。
<button class="focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500">
キーボード操作のときだけリングが出る
</button>
21.4 aria-* / data-* バリアントで状態を見た目に反映
トグルボタンやアコーディオンのような UI では、「押されている」「開いている」といった状態を、見た目とスクリーンリーダーの両方に伝える必要があります。状態は ARIA 属性(aria-pressed・aria-expanded など)で表現し、第6章の aria-* バリアントでその見た目を作ります。
<button aria-pressed="true" class="aria-pressed:bg-blue-600 aria-pressed:text-white">
トグル
</button>
aria-pressed 属性を切り替えれば、見た目(aria-pressed: のスタイル)も支援技術への伝達も同時に正しくなります。「見た目だけ変えて ARIA 属性を更新し忘れる」というありがちなバグを、属性ドリブンにすることで防げます。
21.5 コントラスト・色だけに依存しない設計
色に関するアクセシビリティで重要な点が 2 つあります。
- コントラスト比を確保する: 文字と背景のコントラストが低いと、弱視の人や明るい屋外では読めません。
text-gray-400を白背景に置くような薄い文字は要注意です。目安は WCAG の AA 基準で、通常テキストは 4.5:1 以上です(大きな文字は 3:1 以上など、一部に例外があります)。この基準を満たす配色を選びます。 - 色だけで情報を伝えない: 「赤が必須、緑が任意」のように色だけで区別すると、色覚特性のある人に伝わりません。色に加えて、アイコンやテキスト(「※必須」など)を併用します。エラーを赤くするだけでなく、エラーメッセージの文言も添える、という具合です。
第19章で扱った motion-reduce: への配慮も、アクセシビリティの一環です。装飾的なアニメーションには動きを止める手段をセットにします。
21.6 実務: キーボード操作と Headless UI の活用
複雑な UI(モーダル、ドロップダウン、タブ、コンボボックスなど)を正しくアクセシブルに作るのは、実はとても難しいことです。フォーカスの管理(モーダルの外にフォーカスが出ないようにする)、キーボード操作(矢印キーでメニューを移動、Esc で閉じる)、適切な ARIA 属性——これらを自前で完璧に実装するのは大変です。
ここで頼れるのが、Tailwind Labs 製の Headless UI です。これは「スタイルを持たないが、アクセシビリティと操作性は作り込まれている」UI 部品集です。見た目は持たないので、Tailwind のユーティリティで自由に装飾できます。「面倒で間違えやすいアクセシビリティの部分はライブラリに任せ、見た目は Tailwind で作る」という、理想的な分担ができます(React での活用は第25章で扱います)。
第5部はここまでです。レスポンシブ・ダークモード・アニメーション・フォーム・アクセシビリティという、実プロダクトで必ず問われるテーマを、すべて第2部のバリアントの応用として見てきました。次の第6部では、ここまで何度も「コンポーネント化で解決する」と先送りにしてきた話題——長いクラス列をどう再利用可能な部品にまとめるか——に正面から取り組みます。
参考資料
- Tailwind CSS Docs — Display(sr-only / not-sr-only)
- Tailwind CSS Docs — Hover, focus & other states(focus-visible / aria-*)
- MDN —
:focus-visible - W3C — WCAG 2.2 Contrast (Minimum)
- Headless UI(公式サイト)
演習(第5部)
実践テーマは「条件付きで見た目を変える」練習が中心です。
- ダークモード: §18.3 の
@custom-variant dark (&:where(.dark, .dark *))を設定し、<html>に.darkを付け外しして配色が切り替わるカードを作ってください。さらに、dark:をベタ書きする版と、セマンティックトークン(bg-surfaceなど)で切り替える版の両方を書き、違いを体感しましょう(§18.5)。 - フォームの状態: メール入力欄を作り、
focus:で枠を強調し、peer+peer-invalid:で不正時にエラーメッセージを表示してください(§20.3)。 - アクセシビリティ点検: アイコンだけのボタンに
sr-onlyでラベルを付け、focus-visible:でフォーカスリングを出してください。outline-noneだけで終わらせていないか確認しましょう(§21.2・§21.3)。
第6部 コンポーネント設計
ここまで何度も「繰り返しはコンポーネント化で解決します」と先送りにしてきました。第6部で、その約束を回収します。
第3章で見たとおり、Tailwind の「クラスが長くなる」という弱点は、同じ長いクラス列を何度も書くときに最も痛みになります。ボタンを 50 か所に置くのに、毎回 15 個のクラスをコピーするのは現実的ではありません。この重複をどう減らすか——それがこの部のテーマです。
ただし、減らし方には良い方法と悪い方法があります。第22章でまず「@apply で CSS に逃がす」という一見便利な方法の落とし穴を理解し、なぜコンポーネント抽出が推奨されるのかを腹落ちさせます。第23章では再利用を支える道具(clsx・tailwind-merge・CVA)を、第24章では Rails、第25章では React での具体的な設計を扱います。
第22章 UI コンポーネントを作る
22.1 「重複」をどう捉えるか
まず大前提として、Tailwind における重複は「悪」と決めつけないことが大切です。p-4・bg-white のようなユーティリティが画面のあちこちに現れるのは、第3章で見たとおり設計どおりであって、問題ではありません。生成される CSS は .p-4 ひとつだけなので、いくら使っても CSS は増えません。
問題になるのは、「ボタン」「カード」のような意味のあるまとまりが、長いクラス列ごと何度も複製されるときです。
<!-- 同じボタンを 3 か所に……長いクラス列が複製される -->
<button class="inline-flex items-center rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700">保存</button>
<button class="inline-flex items-center rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700">送信</button>
<button class="inline-flex items-center rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700">更新</button>
これだと、ボタンのデザインを変えるとき 3 か所すべてを直す必要があり、修正漏れも起きます。これこそが「コンポーネント化で解決すべき重複」です。
重複への対処には、大きく 2 つの方向があります。1 つは @apply で CSS にまとめる方法、もう 1 つはテンプレート/コンポーネントとして抽出する方法です。順に見ていきます。
22.2 @apply の役割と落とし穴
@apply は、ユーティリティクラスを自前の CSS クラスの中に展開するディレクティブです(第2部)。
.btn-primary {
@apply inline-flex items-center rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700;
}
<button class="btn-primary">保存</button>
これを見ると、「HTML がすっきりして最高では?」と思うかもしれません。実際、@apply は便利に見えます。しかし、ここには大きな落とし穴があります。
@apply で .btn-primary を作るということは、第1章で見た「セマンティックなクラス名を作る世界」に逆戻りしているということです。考えてみてください。
.btn-primaryという名前を考える必要が再び生まれる(命名コストの復活)。- このクラスは CSS ファイルに存在するので、CSS が再び増えていく(線形に増えない、という利点の放棄)。
- 見た目を知るには、HTML から CSS ファイルへ行き来する必要が戻ってくる。
- 結局、BEM 時代と同じ「グローバルな CSS クラス」を管理することになる。
つまり @apply を多用すると、Tailwind を導入してわざわざ捨てたはずの問題が、そっくり戻ってくるのです。HTML が一瞬きれいに見える代償として、Tailwind の利点をほとんど手放してしまいます。
22.3 @apply よりコンポーネント抽出が推奨される理由
では作者やコミュニティは、重複にどう対処せよと言っているのでしょうか。答えは明確で、@apply ではなく、テンプレートやコンポーネントとして抽出せよ、です。
Tailwind 公式ドキュメントも、重複の管理方法として最初に挙げるのは「テンプレートのループ」や「コンポーネント/部分テンプレートへの抽出」であり、@apply は最後にわずかに触れられるだけです。さらに公式は、@apply について「マークアップを整理したいという理由だけで使うのは避けるべき」という趣旨の注意を添えています。
理由は 22.2 の裏返しです。コンポーネント抽出なら、
- 命名は最小限で済む(
Buttonという部品名は要るが、CSS クラスの命名体系は不要)。 - CSS は増えない(クラス列は HTML/テンプレート側にあるまま)。
- 見た目はその場で分かる(コンポーネントの定義を 1 か所見ればよい)。
- props でバリエーションを表現できる(色やサイズを引数で変えられる。
@applyではできない)。
@apply が正当化されるのは、ごく限られた場面だけです。たとえば、自分でコントロールできない外部のマークアップ(第三者のウィジェットなど)にスタイルを当てる必要があり、かつコンポーネント化できないケース。こうした例外を除けば、重複は CSS にではなく、テンプレート/コンポーネントに畳み込む——これが鉄則です。
22.4 ボタンを題材にした抽出
「抽出」とは具体的にどうすることか。同じボタンを、各環境での部品にまとめてみます。クラス列が現れるのは定義の 1 か所だけになり、使う側はすっきりします。
HTML だけの場合(テンプレートのループ): データを配列にして繰り返せば、クラス列は 1 か所で済みます。
Rails(部分テンプレート):
<%# app/views/shared/_button.html.erb %>
<button class="inline-flex items-center rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700">
<%= label %>
</button>
<%= render "shared/button", label: "保存" %>
React(コンポーネント):
function Button({ children }: { children: React.ReactNode }) {
return (
<button className="inline-flex items-center rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700">
{children}
</button>
)
}
<Button>保存</Button>
どちらも、長いクラス列は部品の中に 1 度だけ書かれ、呼び出し側は render "shared/button" や <Button> と書くだけです。これが「重複を CSS ではなくコンポーネントに畳み込む」ということです。
22.5 カスタムユーティリティとコンポーネントクラスの境界
ここで線引きを整理しておきます。「自前の CSS クラスを作るな」と言っているわけではありません。問題は「何を CSS に置き、何をコンポーネントに置くか」です。
- コンポーネント(ボタン、カードなど意味のあるまとまり) → テンプレート/コンポーネントに抽出する(
@applyで CSS クラスにしない)。 - 本当に汎用的な、単機能の拡張 → v4 の
@utilityでカスタムユーティリティとして定義してよい。
@utility は、Tailwind の流儀に沿った「新しいユーティリティを 1 つ足す」仕組みです(第2部・第26章)。
@utility content-auto {
content-visibility: auto;
}
これは content-auto という単機能のユーティリティを増やすもので、hover: などのバリアントとも組み合わせられます。「ボタン全体」のような複合的なまとまりを 1 クラスに押し込む @apply とは、性質がまったく違います。複合的なまとまりはコンポーネントへ、単機能の拡張は @utility へ——この境界を覚えておけば、CSS を不健全に太らせずに済みます。
参考資料
- Tailwind CSS Docs — Styling with utility classes(Managing duplication / Avoiding @apply)
- Tailwind CSS Docs — Functions and directives(@apply / @utility)
第23章 再利用性を高める
第22章で「コンポーネントに抽出する」方針が固まりました。この章では、抽出したコンポーネントを実用的に使えるものにするための道具を見ます。ボタンに色やサイズのバリエーションを持たせたり、外から追加のクラスを受け取れるようにしたりする工夫です。主に React/JavaScript の文脈ですが、考え方は Rails にも応用できます(第24章)。
23.1 クラス名が長くなる問題への現実的対処
コンポーネントに抽出しても、「中のクラス列が長い」こと自体は変わりません。これは Tailwind の本質的なトレードオフ(第3章)なので、なくすことはできません。現実的な対処は次の通りです。
- 長いクラス列が見えるのは定義の 1 か所だけにする(第22章の抽出)。
- クラスの並び順をそろえる(第9章の
prettier-plugin-tailwindcss)。 - バリアントごとにクラスを整理する(後述の CVA)。
「長さをゼロにする」のではなく「長さを 1 か所に閉じ込めて管理する」のが、実務的なゴールです。
23.2 条件付きクラスの組み立て(clsx / classnames)
コンポーネントでは、「状態に応じてクラスを付け外しする」ことが頻繁にあります。たとえば「選択中なら背景を青く」といった具合です。素朴に書くと、文字列の連結で読みにくくなります。
これを整理するのが clsx(や同種の classnames)です。条件に応じて className 文字列を組み立てる、ごく小さなユーティリティです。
import clsx from 'clsx'
clsx('px-4 py-2', isActive && 'bg-blue-600 text-white', isDisabled && 'opacity-50')
// isActive が true なら 'px-4 py-2 bg-blue-600 text-white'
false や null は自動で無視されるので、三項演算子や && で素直に条件分岐を書けます。
23.3 クラス衝突の解消(tailwind-merge)
clsx には解決できない問題があります。クラスの衝突です。第4章で見たとおり、p-4 と p-8 のように同じプロパティを扱うクラスを両方並べても、勝つのは CSS 上の順序で決まり、HTML の並び順では決まりません。これはコンポーネントに「外から上書き用のクラスを渡したい」ときに困ります。
// 中で p-2、外から p-4 を渡したい → 単純連結だと両方残り、意図通りにならない
<Button className="p-4" /> // 中: 'p-2' + 外: 'p-4' = 'p-2 p-4'
これを解決するのが tailwind-merge です。Tailwind のクラスを賢く解釈し、衝突するものは後勝ちで 1 つにまとめてくれます。
import { twMerge } from 'tailwind-merge'
twMerge('px-2 py-1 bg-red-500', 'p-3 bg-blue-500')
// → 'p-3 bg-blue-500'(px-2 py-1 は p-3 に、bg-red-500 は bg-blue-500 に吸収)
実務では、clsx(条件分岐)と twMerge(衝突解消)を組み合わせた cn というヘルパーを用意するのが定番です。
// よくある cn ヘルパー
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
役割分担は明確です。clsx が「条件に応じて集める」、tailwind-merge が「衝突を解いて最終的な 1 列にする」。この cn が、次章以降のコンポーネント設計の土台になります。
23.4 バリアント設計: Class Variance Authority(CVA)
ボタンには「色(primary / secondary / danger)」「サイズ(sm / md / lg)」のようなバリエーションが付きものです。これを clsx の条件分岐だけで書くと、すぐにごちゃごちゃになります。
ここで使うのが CVA(Class Variance Authority) です。これは「どの props のとき、どのクラスを当てるか」を宣言的に定義できるライブラリで、型安全にバリアントを管理できます(Joe Bell によるメンテナンス)。
import { cva } from 'class-variance-authority'
const button = cva(
// 常に当たる基本クラス
'inline-flex items-center rounded-md font-medium',
{
variants: {
intent: {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
danger: 'bg-red-600 text-white hover:bg-red-700',
},
size: {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-sm',
lg: 'px-6 py-3 text-base',
},
},
defaultVariants: { intent: 'primary', size: 'md' },
}
)
button({ intent: 'danger', size: 'lg' })
// → 基本クラス + danger のクラス + lg のクラス
intent や size という props と、当てるクラスの対応が 1 か所にまとまり、見通しが格段に良くなります。しかも TypeScript で「存在しないバリアントを渡したらエラー」にできるので、安全です。
23.5 デザイントークンを単一の真実にする
ここまでの道具(cn・CVA)は、あくまで「クラスをどう組み立てるか」の話です。その土台として、何色・何 px を使うかは、第5章のテーマ(@theme)に集約しておくべきです。
CVA のバリアントに bg-blue-600 のような直値を書いていても、その blue-600 自体はテーマで定義されたトークンです。さらに第12章で触れたセマンティックトークン(bg-primary など)をテーマに用意しておけば、CVA の定義も bg-primary のように意味で書けて、ブランド変更にも強くなります。「値はテーマ、組み立ては CVA/cn」という二層構造が、保守しやすいコンポーネントの基本形です。
23.6 実務: デザインシステムへの育て方
これらを組み合わせると、自然と「自分たちのデザインシステム」が育ちます。育て方の順序はこうです。
- まずベタ書きで作る(早く動かす)。
- 重複してきたらコンポーネントに抽出する(第22章)。
- バリエーションが増えてきたら CVA で整理する。
- 共通の値はデザイントークンに集約する。
最初から完璧なデザインシステムを作ろうとせず、重複が痛くなってきた箇所から段階的に育てるのが、現実的で失敗しにくいやり方です。
23.7 AI 生成コードとクラス設計
近年は、ChatGPT や Cursor、Copilot のような AI が Tailwind のコードを生成する機会が増えました。AI は Tailwind を出力しやすい(クラスがそのまま見た目を表し、学習データも豊富なため)一方で、長いクラス列をベタ書きで吐き、任意の値やトークンを無視した直値を混ぜがちという傾向があります。
そこで、本章の道具が AI 時代にこそ効きます。AI が生成した長いクラス列を、人間が CVA やコンポーネントへ畳み込み、直値はデザイントークンに寄せて整える。AI には「既存のテーマ変数とコンポーネント規約に従って」と前提を渡す(第26章・付録E)。生成して終わりにせず、本章の設計に取り込むことで、AI の速度と設計の一貫性を両立できます。
参考資料
第24章 Rails でのコンポーネント設計
本書が最重点とする Rails です。Rails には、ビューを再利用可能な部品にまとめる方法が複数あります。それぞれの性格を理解し、Tailwind と相性よく使い分けましょう。
24.1 Rails でのコンポーネント化の選択肢
Rails でのコンポーネント化には、主に 3 つの選択肢があります。
| 方法 | 性格 | 向いている場面 |
|---|---|---|
| 部分テンプレート(partial) | Rails 標準。学習コストゼロ | まず重複を減らしたい。小〜中規模 |
| ViewComponent | Ruby オブジェクトとして部品化。テスト容易 | ロジックを持つ部品、再利用と品質を重視 |
| Phlex | HTML を Ruby で書く | Ruby で完結させたい、合成を多用したい |
重み付けの目安はこうです。まずは partial で十分です。多くのプロジェクトは partial だけで重複を解消できます。部品にロジックやバリアントが増え、テストもしたくなってきたら ViewComponent を検討します。Phlex は、テンプレート言語ではなく Ruby で書きたいチームの選択肢です。いきなり凝った仕組みを入れず、必要に応じて段階的に上げていくのが Rails らしいやり方です。
24.2 部分テンプレートとクラス組み立て
最も基本的な方法が部分テンプレートです。第22章でも見たとおり、クラス列を partial の中に閉じ込めます。バリアントを扱いたいときは、locals で受け取った値に応じてクラスを組み立てます。Ruby 側でクラスを組み立てるとき、Rails の class_names(または token_list)ヘルパーが clsx に近い役割を果たします。
<%# app/views/shared/_button.html.erb %>
<%# 使い方: render "shared/button", label: "削除", intent: :danger %>
<%# intent は省略可能にする(未定義 local の直接参照は NameError になるため) %>
<% intent = local_assigns.fetch(:intent, :primary) %>
<% base = "inline-flex items-center rounded-md px-4 py-2 text-sm font-medium" %>
<% styles = {
primary: "bg-blue-600 text-white hover:bg-blue-700",
danger: "bg-red-600 text-white hover:bg-red-700",
} %>
<button class="<%= class_names(base, styles.fetch(intent)) %>">
<%= label %>
</button>
class_names は、条件付きのクラスや配列を受け取って 1 つの文字列にまとめてくれる Rails 標準のヘルパーです。「Rails だけで clsx 相当のことができる」と覚えておきましょう。
24.3 ViewComponent
partial が大きくなり、ロジックやバリアントが増えてくると、ViewComponent が活きてきます。これは「コンポーネントを Ruby のクラス+テンプレートの組として定義する」Rails 向けの主要なコンポーネントライブラリで、Rails とシームレスに統合でき、再利用・テスト・カプセル化に優れます(Rails 本体の機能ではなく、独立した gem です。現行の v4 は長期サポート対象で、機能的に安定した段階に入っています)。
# app/components/button_component.rb
class ButtonComponent < ViewComponent::Base
BASE = "inline-flex items-center rounded-md px-4 py-2 text-sm font-medium"
STYLES = {
primary: "bg-blue-600 text-white hover:bg-blue-700",
danger: "bg-red-600 text-white hover:bg-red-700",
}.freeze
def initialize(label:, intent: :primary)
@label = label
@intent = intent
end
def classes
class_names(BASE, STYLES.fetch(@intent))
end
end
<%# app/components/button_component.html.erb %>
<button class="<%= classes %>"><%= @label %></button>
<%= render(ButtonComponent.new(label: "削除", intent: :danger)) %>
クラスの組み立てロジックが Ruby のメソッドに収まり、コンポーネント単体でテストできるのが大きな利点です。「このコンポーネントは danger のとき赤背景のクラスを持つ」といったテストが書けます。バリアントの種類が多い部品ほど、この構造が効きます。
24.4 Phlex
Phlex は、テンプレート言語(ERB)を使わず、HTML を Ruby のコードとして書くライブラリです。ビューを Ruby オブジェクトとして組み立てたいチームに向きます。
class Button < Phlex::HTML
def initialize(label:, intent: :primary)
@label = label
@intent = intent
end
def view_template
button(class: classes) { @label }
end
private
def classes
base = "inline-flex items-center rounded-md px-4 py-2 text-sm font-medium"
styles = { primary: "bg-blue-600 text-white", danger: "bg-red-600 text-white" }
"#{base} #{styles[@intent]}"
end
end
Ruby のメソッドや継承でビューを合成できるため、共通のレイアウトを継承して個別ボタンを作る、といったオブジェクト指向的な再利用が自然にできます。Tailwind のクラスは、ただの文字列として Ruby の中で組み立てればよいので、相性は良好です。
24.5 Rails で CVA 的なバリアント管理を実現する
React の CVA(第23章)に当たる「宣言的なバリアント管理」は、Rails でも Ruby のハッシュとヘルパーで十分に表現できます。専用ライブラリがなくても、考え方を移植すればよいのです。
# 共通化したバリアントビルダーの例
module ButtonStyles
BASE = "inline-flex items-center rounded-md font-medium"
INTENT = {
primary: "bg-blue-600 text-white hover:bg-blue-700",
danger: "bg-red-600 text-white hover:bg-red-700",
}.freeze
SIZE = { sm: "px-3 py-1.5 text-sm", md: "px-4 py-2 text-sm", lg: "px-6 py-3" }.freeze
def self.call(intent: :primary, size: :md)
[BASE, INTENT.fetch(intent), SIZE.fetch(size)].join(" ")
end
end
# 使い方
ButtonStyles.call(intent: :danger, size: :lg)
CVA の variants/defaultVariants を、ハッシュと fetch、デフォルト引数で再現しています。ViewComponent と組み合わせれば、型こそ付かないものの、CVA とよく似た見通しのよいバリアント管理ができます。
24.6 Hotwire(Turbo / Stimulus)と動的クラス操作
Rails の標準フロントエンドである Hotwire(Turbo / Stimulus)と Tailwind の組み合わせも押さえましょう。
第8章で触れたとおり、Turbo がページを部分的に差し替えても、そのクラスがソースコード中に文字列として存在していればスタイルは正しく当たります。動的に組み立てたクラス名が検出されないという原則(第4章・第27章)は、ここでも同じです。
ダークモードのトグル(第18章)やメニューの開閉のようなクラスの付け外しは、Stimulus コントローラーで行うのが Rails 流です。
// app/javascript/controllers/toggle_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
toggle() {
document.documentElement.classList.toggle("dark")
}
}
第6章で見た data-* バリアントは、Stimulus が付与する data- 属性と非常に相性が良く、「開いているときだけスタイルを変える」といった表現を、JavaScript を最小限にして書けます。
24.7 実務: 既存 Rails アプリへの段階的導入
既存の Rails アプリに Tailwind を入れるときは、一気に全部を置き換えようとしないことが肝心です。おすすめの順序は、(1) tailwindcss-rails を導入(第8章)→ (2) 新しく作る画面から Tailwind で書く → (3) 既存画面は触るついでに少しずつ移行、です。既存の CSS との衝突が心配なら、第4章で触れた prefix で名前空間を分ける手もあります。コンポーネント化も同様に、まず partial、痛くなったら ViewComponent、と段階的に育てれば、移行のリスクを抑えられます。
参考資料
第25章 React でのコンポーネント設計
React では、第23章の道具(cn・CVA)がそのまま主役になります。この章では、それらを使った実践的なコンポーネント設計と、Headless UI・shadcn/ui という 2 つの重要なエコシステムを扱います。
25.1 props でバリアントを受けるコンポーネント設計
React のコンポーネントは、props でバリエーションを受け取るのが基本です。第23章の CVA と組み合わせると、型安全で見通しのよいボタンが作れます。
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from './cn'
const button = cva('inline-flex items-center rounded-md font-medium', {
variants: {
intent: {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
},
size: { sm: 'px-3 py-1.5 text-sm', md: 'px-4 py-2 text-sm' },
},
defaultVariants: { intent: 'primary', size: 'md' },
})
type ButtonProps = React.ComponentProps<'button'> & VariantProps<typeof button>
export function Button({ intent, size, className, ...props }: ButtonProps) {
return <button className={cn(button({ intent, size }), className)} {...props} />
}
25.2 cn ヘルパー(clsx + tailwind-merge)の定番パターン
上のコードの cn(button({ intent, size }), className) が肝心です。これは第23章で作った cn(clsx + tailwind-merge)です。
button({ intent, size })が CVA で組み立てたクラス列を返す。- そこに、利用側から渡された
classNameを後ろに足す。 cnの中のtailwind-mergeが、衝突を後勝ちで解消する。
これにより、<Button className="rounded-full"> のように利用側が一部だけ上書きしたとき、CVA 既定の rounded-md を rounded-full がきれいに置き換えます。「コンポーネントの既定 + 利用側の上書き」を破綻なく合成する——この cn パターンが、現代の React × Tailwind の標準形です。
25.3 CVA による型安全なバリアント
CVA の VariantProps<typeof button> を使うと、コンポーネントの props 型がバリアント定義から自動で導出されます。intent に 'primary' | 'secondary' 以外を渡すと、TypeScript がコンパイル時にエラーにしてくれます。バリアントの定義(クラス)と型が 1 か所に集約され、ずれが起きないのが大きな利点です。
25.4 Headless UI / Radix + Tailwind の組み合わせ
第21章で触れたとおり、モーダルやドロップダウンのような複雑な UI をアクセシブルに作るのは難しい仕事です。フォーカス管理、キーボード操作、ARIA 属性——これらを自前で完璧にやるのは現実的ではありません。
そこで、Headless UI(Tailwind Labs 製)や Radix UI のような「スタイルを持たないが、アクセシビリティと挙動は作り込まれている」ライブラリを使います。これらは見た目を持たないので、Tailwind のユーティリティで自由に装飾できます。
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
<Menu>
<MenuButton className="rounded-md bg-blue-600 px-4 py-2 text-white">
オプション
</MenuButton>
<MenuItems className="mt-2 rounded-md border bg-white shadow-lg">
<MenuItem>
{/* キーボード操作・フォーカス・ARIA はライブラリが面倒を見る */}
<a className="block px-4 py-2 data-focus:bg-gray-100" href="#">編集</a>
</MenuItem>
</MenuItems>
</Menu>
挙動とアクセシビリティはライブラリ、見た目は Tailwind という分担が、最も効率がよく、間違いも起きにくいやり方です。
25.5 shadcn/ui に見る「コピーして所有する」コンポーネント観
React × Tailwind のエコシステムで、いま最も影響力があるのが shadcn/ui です。これは、従来の UI ライブラリとは思想がまったく違う点を理解することが重要です。
従来の UI ライブラリは、npm でインストールして「依存パッケージとして使う」ものでした。中身はブラックボックスで、カスタマイズはライブラリが許した範囲に限られます。
shadcn/ui はそうではありません。これは「コンポーネントのコードを、自分のプロジェクトにコピーして取り込む」という形をとります。コピーした時点で、そのコードはあなたのものになります。中身は Tailwind のクラスと、第23章で見た cn・CVA・Radix などで書かれた、ふつうの React コンポーネントです。だから、
- 中身が完全に見えて、好きなように書き換えられる(Open Code)。
- ライブラリのバージョンアップに振り回されない。
- 第23章で学んだ道具がそのまま使われているので、構造を理解できる。
shadcn/ui は自身を「インストールする UI ライブラリではなく、自分の UI ライブラリの作り方」だと位置づけています。「完成品を借りる」のではなく、「良い出発点をコピーして所有し、育てる」もの——この捉え方ができると、shadcn/ui を正しく使えます。逆に「便利な部品集」として依存し、中身を理解しないままコピーを増やすと、結局メンテナンスできないコードを抱えることになります。
25.6 Next.js での注意(Server Components・動的クラス生成の回避)
最後に Next.js 特有の注意です。第8章で触れたとおり、App Router ではコンポーネントがサーバーコンポーネントとして動くことがあります。ただしスタイリングの観点では、サーバー/クライアントのどちらで動いても、Tailwind が静的にクラスを走査する仕組み(第4章)は変わりません。したがって「クラス名を動的に組み立てない(bg-${color} のように書かない)」という原則も同じく適用されます(理由と完全なクラス名へのマッピング例は §27.3)。
React 固有のうれしい点として、第23章の CVA を使うと「props を完全なクラス名に対応づける」形が自然に守られるため、この落とし穴を避けやすくなります。
第6部はここまでです。@apply に逃げず、コンポーネントに抽出し、cn・CVA・デザイントークンで再利用可能に育て、Rails では partial → ViewComponent / Phlex、React では Headless UI / shadcn/ui を活用する——Tailwind を「大量のクラス」から「整然としたデザインシステム」へと昇華させる道筋を見てきました。次の第7部では、これらを実務のプロジェクト全体で運用するための構成・アンチパターン・評価に踏み込みます。
参考資料
- shadcn/ui(公式サイト・ドキュメント)
- Headless UI(公式サイト)
- Radix Primitives(公式サイト)
- Class Variance Authority(公式サイト)
- tailwind-merge(GitHub)
演習(第6部)
設計の良し悪しは「リファクタリングしてみる」と分かります。
- 抽出する: 同じ長いクラス列を持つボタンが 3 か所にコピーされた状態を作り、それを Rails の partial(または React コンポーネント)に抽出してください。
@applyで CSS クラスにする案と比べ、なぜ抽出の方が良いのか §22.2 の観点で説明できますか。 - バリアント設計:
intent(primary/danger)とsize(sm/md)を持つボタンを、React なら CVA +cnで、Rails なら Ruby のハッシュ+class_namesで実装してください(§23.4・§24.5)。 - 所有する: shadcn/ui のボタンを 1 つ取り上げ、「これは依存ライブラリではなくコピーして所有するもの」とはどういう意味か、中身(
cn・CVA・Radix)を読んで説明してください(§25.5)。
第7部 実務での Tailwind CSS
ここまでで、Tailwind の思想・仕組み・使い方・コンポーネント設計を学んできました。第7部では、それらを実際のプロジェクトで長く運用していくための視点を扱います。
実務では「動けばよい」では済みません。チームで書き、何年も保守し、新しいメンバーが加わる——そうした現実のなかで Tailwind をどう構成し(第26章)、どんな失敗を避け(第27章)、そもそも Tailwind は本当に良い選択なのか(第28章)を、正直に見ていきます。特に第28章では、Tailwind を擁護するのではなく、批判にも正面から向き合い、公平に評価することを目指します。
第26章 プロジェクト構成
26.1 CSS エントリの構成
Tailwind プロジェクトの心臓は、たった 1 つの CSS エントリファイルです。ここがとっ散らかると全体が荒れるので、書く順序を決めておくことが運用の第一歩です。おすすめの並びはこうです。
/* app/assets/tailwind/application.css などのエントリ */
/* 1. Tailwind 本体 */
@import "tailwindcss";
/* 2. 公式・サードパーティのプラグイン */
@plugin "@tailwindcss/typography";
/* 3. テーマ(デザイントークン)の定義 */
@theme {
--color-brand: oklch(0.45 0.24 264);
--font-display: "Inter", sans-serif;
}
/* 4. 追加で走査したいソース(必要時のみ) */
@source "../../../node_modules/some-ui-lib";
/* 5. カスタムユーティリティ・ベーススタイル */
@utility content-auto {
content-visibility: auto;
}
「本体 → プラグイン → テーマ → ソース指定 → 自前の追加」という順序を固定しておくと、どこに何を書くか迷いません。チームで共有する規約として、ファイル冒頭にコメントでこの構成を書いておくのも有効です。
26.2 テーマ・トークンの置き場所と命名規約
第5章で見たテーマ変数は、プロジェクトの「デザインの真実」です。実務では、命名規約を決めておくと一貫性が保てます。
- 原始トークン(色そのもの):
--color-blue-600のような、Tailwind 標準に沿った名前。 - セマンティックトークン(役割の名前):
--color-primary・--color-surface・--color-dangerのような、意味を表す名前(第12章)。
実務では、セマンティックトークンを中心に使う設計が保守に強くなります。「プライマリは青」という決定を 1 か所に集約でき、ブランド変更やダークモード(第18章)にも一括で対応できるからです。トークンが増えてきたら、テーマ定義を別ファイルに切り出して @import するのもよいでしょう。
26.3 カスタムユーティリティ/コンポーネント/プラグインの置き分け
ここは混乱しやすいので、「何をどこに置くか」を整理します。第22章の境界の話を、構成の観点で具体化したものです。
| 作りたいもの | 使う仕組み | 置き場所 |
|---|---|---|
| 単機能の新しいユーティリティ | @utility(第2部) | CSS エントリ |
| 複合的な部品(ボタン等) | コンポーネント(第6部) | テンプレート/コンポーネント側 |
| ベースの土台スタイル | @layer base | CSS エントリ |
| 既存の JS プラグイン | @plugin | CSS エントリ |
@plugin と公式プラグインについて。 @tailwindcss/typography(第11章)や @tailwindcss/forms(第20章)は、JavaScript で書かれたプラグインです。v4 では、これらを CSS エントリで @plugin "..." と読み込みます。@plugin は、こうした JavaScript ベースのプラグインを CSS から読み込むための互換的なディレクティブという位置づけです。テーマのカスタマイズ自体は @theme(第5章)で CSS だけで完結しますが、既存の JS プラグインを使いたいときに @plugin を用います。
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";
v3 を知っている人へ: v3 ではプラグインを
tailwind.config.jsのplugins: [...]に書きました。v4 ではこれが CSS の@pluginディレクティブに移りました。
ここで役割分担をはっきりさせておきます。第11章・第20章では公式プラグインの使い方(prose や form-input をどう書くか)を扱いました。本章で扱うのは、それらをプロジェクトのどこで管理するか(CSS エントリの @plugin に集約する)という運用面です。
26.4 大規模化への備え
プロジェクトが大きくなると、いくつか備えが要ります。
- ファイル分割: テーマやカスタムユーティリティが膨らんだら、
@import "./theme.css";のように分割します。 @sourceの活用: 自動コンテンツ検出(第4章)から外れる場所(外部パッケージ内のテンプレートなど)のクラスを拾うには@sourceを足します。逆に走査不要な巨大ディレクトリは@source notで除外し、ビルドを軽くできます。- モノレポ: 複数アプリで共通のデザインを使うなら、テーマ定義を共有パッケージに切り出し、各アプリの CSS エントリから読み込む構成が有効です。
26.5 CI でのビルドと出力サイズの監視
Tailwind は使ったクラスだけを生成する(第4章)ので、CSS は基本的に小さく保たれます。とはいえ、任意の値の乱発などで肥大化することはあるため、CI で出力 CSS のサイズを監視しておくと、異常な増加に早く気づけます。ビルドが通ることと、生成サイズが極端に増えていないことを、CI のチェックに含めるとよいでしょう。
なお、コンポーネントの見た目が崩れていないかの確認は、必要に応じて Storybook や視覚回帰テストで行います。ただしこれらは Tailwind 固有の話ではなくフロントエンド運用一般の話題なので、本書では深入りせず「そういう手段がある」とだけ押さえておきます。
26.6 チームでのクラス順統一・レビュー観点
チーム開発では、クラスの並び順を統一すると、レビューが格段に楽になります。第9章の prettier-plugin-tailwindcss を CI/コミット時に効かせ、並び順を自動でそろえるのが定石です。これで「並べ方」を巡る不毛な議論と差分がなくなります。
レビューでは、次のような観点を持つとよいでしょう。
- 任意の値(
[...])が乱発されていないか(第27章)。 - 動的に組み立てたクラス名がないか(第4章・第27章)。
- 直値ではなくデザイントークンを使っているか。
- 同じ長いクラス列が複製されていないか(コンポーネント化の機会、第6部)。
26.7 AI が従えるテーマ・規約の作り方
第23章で触れたとおり、AI による Tailwind コード生成が一般的になりました。AI に「ブランドに沿った、規約どおりの」コードを書かせるには、AI が参照できる形で前提を整えておくことが効きます。
- テーマを意味で定義する:
@themeでセマンティックトークン(--color-primaryなど)を定義しておけば、AI に「bg-primaryを使って」と指示でき、勝手にbg-blue-500を選ぶのを防げます。 - コンポーネント規約を言語化する: 「ボタンは
Buttonコンポーネントを使う」「色は直値ではなくトークン」「動的クラス名は禁止」といった規約を、README や AI 向けの指示ファイルに明文化しておきます。 - 出力形式を決めておく: Rails ERB か React(tsx) か、CVA を使うか、などを規約化しておくと、生成物のばらつきが減ります。
要するに、第26章で整えた「構成と規約」そのものが、AI に良いコードを書かせるための土台になります。具体的なプロンプト例は付録Eにまとめます。
参考資料
- Tailwind CSS Docs — Functions and directives(@plugin / @utility / @layer)
- Tailwind CSS Docs — Detecting classes in source files(@source)
- Tailwind CSS Docs — Theme
第27章 Tailwind CSS のアンチパターン
この章は「禁止集」ではありません。挙げる項目は、どれも「絶対に使うな」というものではなく、「なぜ問題になりやすいか、いつなら許容できるか、どう判断するか」を示すものです。アンチパターンの多くは、道具自体が悪いのではなく、使いどころを誤ったときに痛むものです。判断基準として読んでください。
27.1 任意の値([...])の乱用
何が起きるか: p-[13px]・text-[#1a1a1a]・w-[327px] のような任意の値(第4章)が画面中に散らばると、第3章で得た「スケールという制約」が崩れ、デザインの一貫性が失われます。
なぜ問題か: 任意の値は「スケールから外れている」サインです。1 つ 2 つなら問題ありませんが、増えてくると「テーマが整っていない」「デザインがスケールに沿っていない」という、より根本的な問題の兆候です。
判断基準: 「この値は本当に唯一無二か?」と問います。p-4 や p-5 で代用できないか。何度も使う値なら、[...] で散らすのではなく、テーマ変数として名前を付ける(第5章)。単発・例外的なら可、繰り返すならテーマへ、が基準です。
27.2 @apply でユーティリティを CSS に逃がしすぎる
何が起きるか: HTML をきれいに見せたいあまり、@apply で何でも CSS クラスにまとめてしまう(第22章)。
なぜ問題か: 第22章で詳しく見たとおり、Tailwind を導入して捨てたはずの「命名コスト」「CSS の肥大化」「ファイルの往復」が戻ってきます。
判断基準: 重複はまずコンポーネント抽出で解決できないかを考える(第6部)。@apply が正当なのは「コンポーネント化できない外部マークアップ」などの例外だけ。重複は CSS にではなくコンポーネントに畳み込む、が基準です。
27.3 クラス名の動的文字列結合で検出漏れ
何が起きるか: text-${color}-600 のようにクラス名を動的に組み立てると、スタイルが当たりません。
なぜ問題か: 第4章で見たとおり、Tailwind はソースをただのテキストとして走査し、完全なクラス名の文字列だけを拾います。text-${color}-600 は 1 つの文字列として認識されず、CSS が生成されません。これは「たまに動いてたまに動かない」ではなく、原理的に生成されないので、初学者が最もハマる罠です。
判断基準: クラス名はつねに完全な文字列で書く。プロパティで切り替えるなら、完全なクラス名のマッピングを用意する(第25章)。
// NG
<div className={`text-${color}-600`} />
// OK
const C = { red: 'text-red-600', blue: 'text-blue-600' }
<div className={C[color]} />
これは判断の余地が小さい、ほぼ絶対のルールです。
27.4 「巨大コンポーネント 1 個」化(抽出粒度の誤り)
何が起きるか: 重複を嫌うあまり、何でも 1 つの巨大なコンポーネントに詰め込み、無数の props で分岐させる。
なぜ問題か: コンポーネント化は良いことですが、粒度を誤ると逆効果です。1 つの <Card> が 30 個の props を持ち、内部が条件分岐だらけになると、もはや誰も理解・修正できません。これは「重複を避ける」ことに囚われすぎた結果です。
判断基準: 「少しの重複は、誤った抽象化よりマシ」という原則を思い出します。無理に共通化せず、似て非なるものは別コンポーネントに分ける勇気を持つ。バリアントは CVA(第23章)で整理できる範囲に留める、が基準です。
27.5 デザイントークンを無視した直値の散乱
何が起きるか: text-[#1a1a1a]・bg-[#fafafa] のような直値の色・サイズが、テーマを無視して散らばる。
なぜ問題か: 27.1 の色版です。直値で書くと、ブランド変更やダークモード対応(第18章)のときに全置換が必要になり、保守不能に近づきます。
判断基準: 色・余白・フォントなど、デザインの基幹となる値はデザイントークンに集約する(第5章・第26章)。とくに色は、ダークモードのことを考えると直値を避けるべき筆頭です。
27.6 既存 CSS との二重管理
何が起きるか: Tailwind を導入したのに、従来の CSS ファイルも併存し、同じ要素が両方で別々にスタイリングされる。
なぜ問題か: 「この余白は Tailwind のクラス? それとも CSS ファイル?」と、見るべき場所が 2 つになります。第1章で見た「どこで効いているか分からない」状態に逆戻りします。
判断基準: 段階的移行(第24章)は問題ありませんが、「この画面は Tailwind に寄せる」と単位を決めて移行し、中途半端な二重管理を長期間放置しない。新規分は Tailwind、と方針を統一する、が基準です。
27.7 アンチパターンの矯正手順
すでにこれらのアンチパターンに陥っている場合の、現実的な直し方です。
- 検出漏れ(27.3)から直す: スタイルが当たっていないバグなので最優先。動的クラス名を完全な文字列に置き換える。
- 直値・任意値をテーマに寄せる(27.1, 27.5): 繰り返し使われている直値を洗い出し、デザイントークンに昇格させる。
- 重複をコンポーネント化(27.2, 27.4): 痛い箇所から段階的に抽出する。一度に全部やろうとしない。
完璧を目指さず、痛いところから順に直すのが、挫折しないコツです。
27.8 AI 生成 Tailwind のよくある崩れ方
AI が生成した Tailwind コードには、本章のアンチパターンがまとめて現れがちです。代表的な崩れ方は次のとおりです。
- 任意の値の乱用(27.1): AI はデザインカンプの値をそのまま
[327px]のように出しがち。 - トークン無視の直値(27.5):
bg-blue-500・p-4を、プロジェクトのトークンを知らずに選ぶ。 - 動的クラス名(27.3): props から
bg-${color}を組み立てるコードを平気で書く。 - レスポンシブ・アクセシビリティの欠落: 見た目は整っていても、
sm:/md:の設計やaria-*・フォーカス可視化(第21章)が抜けがち。
レビュー観点としては、(1) クラスが実在するか、(2) レスポンシブが破綻していないか、(3) アクセシビリティを落としていないか、(4) 色・余白・角丸・影がトークンに沿っているか、(5) text-${color} のような検出漏れがないか、をチェックします。AI の出力は「下書き」として受け取り、本章の基準で整える——これが AI 時代の実務作法です。
参考資料
- Tailwind CSS Docs — Detecting classes in source files(動的クラス名の注意)
- Tailwind CSS Docs — Styling with utility classes(@apply / 重複の管理)
- Tailwind CSS Docs — Theme(トークンへの集約)
第28章 Tailwind CSS への批判と評価
Tailwind は広く使われていますが、同時に強い批判も浴び続けてきました。この章では、Tailwind を擁護するのではなく、主要な批判をできるだけ強い形で提示し、それに対する反論を並べ、最後に公平な評価を下します。読者が「自分のプロジェクトでどうすべきか」を自分で判断できるようにするのが目的です。
28.1 批判①「HTML が汚い」
批判の主張: Tailwind の最大の批判はこれです。class="inline-flex items-center rounded-md bg-blue-600 px-4 py-2 ..." のようにクラスが大量に並んだ HTML は、読みにくく、醜く、保守しづらい。要素の構造がクラスの洪水に埋もれて見えなくなる、というものです。これは見た目の好みの問題に留まらず、「ぱっと見て構造を把握できない」という実害を伴います。
反論: 第3章で見たとおり、これはトレードオフだという立場です。HTML が「にぎやか」になる代わりに、CSS ファイルの肥大化・命名・詳細度の戦いから解放される。そして、繰り返し現れる長いクラス列はコンポーネント化すれば、それが見えるのは定義の 1 か所だけになる(第6部)。
評価: この批判には、正当な部分があります。コンポーネント化の仕組みがない環境(生の HTML を手で量産するなど)では、「醜い HTML」は実害のある負債になります。一方、React や Rails の ViewComponent のように抽出手段が整った環境では、痛みは大きく緩和されます。つまりこの批判の妥当性は、プロジェクトの構成に依存する、というのが公平な見方です。「コンポーネント化できる環境かどうか」が分かれ目です。
28.2 批判②「インラインスタイルと同じでは」
批判の主張: クラスで見た目を直接指定するなら、style 属性に書くインラインスタイルと本質的に同じで、CSS の進歩を巻き戻しているだけだ、というものです。
反論: 第3章で詳しく見たとおり、これは事実として誤りです。Tailwind のユーティリティは、インラインスタイルにはできない 3 つのことができます——制約(スケールによる一貫性)、状態(hover: など)、レスポンシブ(md: など)。公式ドキュメントもこの 3 点を明確に挙げています。インラインスタイルでは :hover も @media も書けません。
評価: この点については、反論に分があります。「見た目を要素に直接当てる」表層は似ていても、Tailwind が提供する一貫性・状態・レスポンシブは、インラインスタイルには原理的にない価値です。ただし「魔法の数字(magic number)を散らかしうる」という危うさは、任意の値の乱用(第27章)として現実に存在するため、批判が全く的外れというわけでもありません。
28.3 批判③「関心の分離に反する」
批判の主張: HTML(構造)と CSS(見た目)は分離すべきであり、見た目を HTML に書き込む Tailwind は、長年確立されてきた「関心の分離」の原則に反する、というものです。これは最も思想的で根深い論争です。
反論: Adam Wathan の主張(第2章)は、「分けるべきは HTML と CSS ではなく、再利用できるものとできないものだ」という関心の分離の再定義でした。.hero-title のようなクラスは、ファイルが分かれていても HTML の構造に CSS が依存しており、本当の意味では分離していない、と。
評価: ここはどちらが正しいと断定できない、価値観の対立です。「構造と表現の分離」を重んじる立場からは Tailwind は退歩に見え、「再利用性と変更容易性」を重んじる立場からは前進に見えます。重要なのは、これが技術的な優劣ではなく、何を重視するかの選択だと理解することです。本書は Tailwind を解説する本ですが、セマンティックな CSS(BEM など)の思想にも合理性があることは、公平に認めるべきです。
28.4 批判④「学習コスト」
批判の主張: 既存の CSS 知識があっても、Tailwind 独自のクラス名(flex はともかく shrink-0・inset-0・tracking-tight など)を新たに覚える必要があり、学習コストが高い。CSS を知っていれば素の CSS の方が早い、というものです。
反論: クラス名の多くは CSS プロパティの短縮形であり(第15章・第16章の対応表)、エディタの補完(第9章)があれば暗記は不要。一度慣れれば、命名やファイル往復が消える分むしろ速い、という反論です。
評価: 短期的には批判が正しく、長期的には反論が正しい、というのが実態に近いでしょう。導入直後は確かに遅くなります。CSS に習熟した人ほど「自分の知識が使えない」もどかしさを感じます。しかし数週間で慣れ、その後は生産性が上がるという声が多い。「最初は遅い」というコストは実在するので、チーム導入時はこの立ち上がり期間を見込む必要があります。
28.5 批判⑤「ロックインと移植性」
批判の主張: HTML が Tailwind のクラスで埋め尽くされると、後から Tailwind をやめるのが極めて困難になる。特定のツールにロックインされ、移植性が下がる、というものです。
反論: ユーティリティは結局ただの CSS クラスであり、最終的な生成物は標準的な CSS と HTML です。また、これは Tailwind 固有の問題ではなく、CSS-in-JS でも独自の命名規則でも、何らかの方式を採用すれば程度の差こそあれ移行コストは生じる、という反論です。
評価: この批判には現実的な重みがあります。Tailwind をやめる場合、HTML に散らばったクラスを別の方式へ移すのは大仕事です。ただし「移行が大変」なのはどんな CSS 方針でも同じで、Tailwind だけが特別に悪いわけではありません。公平に言えば、「採用は容易だが撤退は重い」という性質を理解したうえで選ぶべき、ということになります。導入前に「このプロジェクトで Tailwind を長く使い続ける見込みがあるか」を考えるのが賢明です。
28.6 計測できる効果の実態
主観的な論争だけでなく、計測しやすい効果も見ておきましょう。
- CSS サイズ: 第3章・第4章で見たとおり、CSS は使うユーティリティの種類数で頭打ちになり、プロジェクトが大きくなっても線形には増えません。大規模・長期のプロジェクトほど、この効果は明確です。
- 変更容易性: 「この CSS を消すと何が壊れるか分からない」という恐怖(第1章)が小さくなります。ユーティリティは局所的で、他へ波及しないからです。
- 開発速度: 立ち上がり後は、命名とファイル往復が消える分、速くなるという報告が多い。ただしこれは定量化が難しく、チームや個人差が大きい点は留意が必要です。
これらは「Tailwind の宣伝文句」ではなく、仕組みから説明できる性質です。とはいえ、どの効果も「プロジェクトの規模・寿命・チーム」に強く依存します。小さく短命なサイトでは、これらの利点はほとんど効きません。
28.7 結論: 向くプロジェクト・向かないプロジェクト
公平な評価を踏まえた結論です。
向いている:
- 中〜大規模で、長く保守されるアプリケーション。
- React / Vue や Rails の ViewComponent など、コンポーネント化の手段がある環境。
- デザインの一貫性をチームで仕組みとして担保したい場合。
慎重になるべき:
- ごく小さく短命な静的ページ(利点が効かず、学習コストだけ残る)。
- コンポーネント化の仕組みがなく、長いクラス列を手でコピーし続ける環境。
- チームが強くセマンティック CSS の思想を支持しており、その合意がある場合(無理に変える必要はない)。
Tailwind は「あらゆる場面で最善」ではありません。強みと弱みを理解し、プロジェクトの性質と照らして選ぶべき道具です。具体的な採用判断のチェックリストは、次の第8部・第31章にまとめます。
参考資料
- Tailwind CSS Docs — Styling with utility classes(「インラインスタイルと同じか」への公式の回答)
- Adam Wathan「CSS Utility Classes and "Separation of Concerns"」(関心の分離の再定義)
- BEM 公式(セマンティック CSS 側の思想)
演習(第7部)
実務視点は「判断できるか」が問われます。
- 構成を設計する: 架空の Rails アプリの CSS エントリ(
application.css)を、§26.1 の順序(本体 → プラグイン → テーマ → ソース → 自前)で書いてみてください。@themeにはセマンティックトークンを 3 つ定義しましょう。 - アンチパターンを診断する: 次のコードの問題点を第27章の観点で挙げ、直し方を述べてください —
<div className={\text-${color}-600 p-[13px]`}>`。 - 批判に答える: 「Tailwind は HTML が汚いから使いたくない」と言う同僚に対し、第28章を踏まえて、反論だけでなく相手の正当な部分も認めた上で、どう応答するか書いてみてください。
第8部 Tailwind CSS の未来
最後の部です。ここまでで Tailwind の過去(第1部)・仕組み(第2部)・実践(第3〜6部)・実務での評価(第7部)を見てきました。第8部では視線を現在と未来に向けます。
まず、本書が前提としてきた v4 が何を変えたのかを、ここで腰を据えて総括します(第29章)。次に、Tailwind が属する Utility First の潮流が、ネイティブ CSS の進化や AI による UI 生成のなかでどこへ向かうのかを展望します(第30章)。そして最後に、ここまでの全内容を踏まえ、あなたのプロジェクトで Tailwind を選ぶべきかという最も実務的な問いに、判断材料を添えて答えます(第31章)。
第29章 Tailwind v4
29.1 v4 が目指したもの
第2章で見たとおり、v4 は「コードネーム Oxide」と呼ばれた新エンジンの開発から始まり、当初は v3.x の一部として計画されていたものが、あまりに大きな変化のため v4.0 として 2025 年 1 月にリリースされました。v4 が目指したのは、大きく 3 つです。
- 速度: ビルドを劇的に速くする。
- モダン CSS の全面採用: ここ数年で進化した CSS の新機能を土台にする。
- 設定の簡素化: 設定を CSS に寄せ、周辺ツールを減らす。
本書はこの v4 を一貫して前提にしてきました。この章では、これまで各所で触れてきた v4 の変更点を、ひとつの見取り図としてまとめます。
29.2 新エンジン(Oxide / Lightning CSS)の内部
v4 のエンジンは、性能が重要な部分を Rust で書き直したものです(第4章)。あわせて、CSS のパース・ベンダープレフィックス付与・@import の解決・圧縮を、Rust 製の高速ツール Lightning CSS が一手に担います。
この刷新により、公式(v4.0 時点)の計測ではフルビルドが約 3.8 倍、増分ビルドが約 8 倍以上速くなりました。同時に、以前は別途必要だった postcss-import や autoprefixer が Tailwind 単体に取り込まれ、周辺ツールの設定が減ったのも大きな実利です(第7章)。
29.3 CSS ファースト設定への全面移行
v4 最大の思想的変化が、設定を JavaScript から CSS へ移したことです(第5章)。
/* v4: 設定は CSS の中に書く */
@import "tailwindcss";
@theme {
--color-brand: oklch(0.45 0.24 264);
--breakpoint-3xl: 120rem;
}
v3 までの tailwind.config.js(JavaScript)は必須ではなくなりました。「CSS のための設定を、CSS で書く」という自然な形への回帰です。しかも第5章で見たとおり、@theme の変数はそのまま CSS 変数として公開されるため、設定とランタイムの値が一致します。
29.4 モダン CSS の全面採用
v4 は「自分で何でも実装する」のではなく、進化したブラウザのネイティブ機能に乗る方針を取りました。具体的には、
- カスケードレイヤー(
@layer): 詳細度の戦いを構造的に解決(第4章)。 - 登録済みカスタムプロパティ(
@property): アニメーション可能な CSS 変数などを実現。 color-mix():bg-black/50の不透明度合成に使用(第12章)。- 論理プロパティ:
padding-inlineなどで多言語(RTL)対応(第10章)。
これは「Tailwind が薄くなった」ことを意味します。ブラウザができることはブラウザに任せ、Tailwind はそれを使いやすくする層に徹する——この方向性は第30章のテーマにもつながります。
29.5 Container Queries・3D transform・@starting-style の標準化
v3 ではプラグインが必要だった、あるいは存在しなかった機能が、v4 でコアに入りました。
- Container Queries(
@container/@md:): 親要素の幅に応じたレイアウト(第14章・第17章)。 - 3D transform(
rotate-x-*など): 立体的な変形(第19章)。 @starting-style(starting:バリアント): JavaScript なしの入場アニメーション(第19章)。
いずれも「モダン CSS をユーティリティのまま使えるようにする」という v4 の性格をよく表しています。
29.6 v3 → v4 移行の実務
既存の v3 プロジェクトを v4 に上げるときの要点です。第7章でも触れた移行ツールが主役です。
npx @tailwindcss/upgrade
このツールは大部分を自動で変換してくれます(Node.js 20 以上が必要)。主な破壊的変更は次のとおりです。
| 項目 | v3 | v4 |
|---|---|---|
| 読み込み | @tailwind base; ... | @import "tailwindcss"; |
| 設定 | tailwind.config.js | CSS の @theme(@config で互換読込も可) |
| 影 | shadow-sm / shadow | shadow-xs / shadow-sm |
| 角丸 | rounded-sm | rounded-xs |
| リング | ring(3px) | ring-3(既定は 1px) |
| アウトライン | outline-none | outline-hidden |
| important の位置 | !bg-red-500 | bg-red-500! |
| 既定のボーダー色 | gray-200 | currentColor |
加えて、v4 はモダンブラウザ(Safari 16.4+ / Chrome 111+ / Firefox 128+)が前提です。古いブラウザを手厚くサポートする必要があるプロジェクトは、無理に上げず v3.4 に留まる判断もありえます。移行は「ツールで自動変換 → 差分を確認 → 手で微修正」の順で、小さく進めるのが安全です。
参考資料
- Tailwind CSS v4.0(2025-01-22)
- 「Open-sourcing our progress on Tailwind CSS v4.0」(新エンジン Oxide)
- Tailwind CSS Docs — Upgrade guide(v3 → v4)
第30章 Utility First の未来
30.1 Atomic CSS の潮流における Tailwind の位置
第1章で見たとおり、Tailwind は「ユーティリティを組み合わせる」という Atomic / Functional CSS の系譜(Tachyons など)に連なります。Tailwind が画期的だったのは思想の発明ではなく、制約あるデザインシステム・必要分だけ生成する仕組み・優れた開発体験を組み合わせ、実務で使える完成度に仕上げたことでした。
その結果、Tailwind は Atomic CSS という考え方を「一部の実験」から「主流の選択肢」へと押し上げました。いまや「ユーティリティでスタイリングする」ことは、特殊な流儀ではなく、ごく一般的な選択肢の 1 つです。これは Tailwind が業界に与えた最大の影響と言えます。
30.2 ネイティブ CSS の進化との関係
興味深いことに、Tailwind が普及する一方で、CSS 自体も急速に進化しています。第29章で見たカスケードレイヤー、コンテナクエリ、@property、color-mix()、:has()、ネスト記法——これらはかつて Tailwind や各種ツールが補っていた機能を、ブラウザがネイティブで提供し始めたものです。
ここで「CSS が進化したら Tailwind は不要になるのでは?」という問いが生まれます。答えは微妙です。Tailwind の価値は「CSS にできないことを補う」ことだけではなく、「制約・一貫性・命名からの解放・開発体験」にあります。これらはネイティブ CSS が進化しても、自動では手に入りません。実際 v4 は、ネイティブ CSS の進化を敵ではなく土台として取り込みました(第29章)。Tailwind は「CSS の代替」ではなく「CSS をチームで一貫して書くための層」へと位置づけを移しつつあります。
30.3 他ツールへの影響(UnoCSS など)
Tailwind の成功は、後続のツールにも影響を与えました。代表が UnoCSS です。これは「オンデマンドの Atomic CSS エンジン」で、Tailwind に着想を得つつ、コアのユーティリティを持たず、すべてをプリセットで構成するという、より柔軟・高速な方向を追求しています。
こうしたツールの存在は、「Utility First」という考え方が、もはや Tailwind 1 つに閉じない広い潮流になったことを示しています。Tailwind が道を切り開き、エコシステム全体が育っている、という構図です。
30.4 デザインツール → コードの潮流
もう 1 つの大きな流れが、デザインから直接コードを生成する動きです。Figma などのデザインツールや、AI による UI 生成(v0 など)が、出力として Tailwind のコードを吐くことが増えました。
これは偶然ではありません。次節で述べるように、Tailwind は機械にとっても扱いやすい形をしているからです。「デザイン → Tailwind のコード」という経路が一般化したことで、Tailwind は「人が書く道具」から「人と機械が共有する中間言語」になりつつあります。
30.5 AI UI 生成と Tailwind
第23章・第27章でも触れたとおり、ChatGPT や Cursor、Copilot のような AI は、Tailwind を非常に出力しやすいという性質があります。理由は明快です。
- クラスがそのまま見た目を表す:
bg-blue-600 p-4を見れば結果が分かる。意味を別ファイルに探しに行く必要がない。AI にとっても「見た目とコードの距離」が近い。 - 学習データが豊富: Tailwind のコードは大量に公開されており、AI が学習しやすい。
- 1 ファイルで完結する: HTML(やコンポーネント)の中に見た目が書かれるので、AI が複数ファイルを横断せずに UI を生成できる。
その一方で、AI 生成には第27章で見た崩れ方(任意値の乱用、トークン無視、動的クラス名)が付きものです。だからこそ、Figma / v0 / shadcn/ui 的な「生成 → 取り込み」の流れが重要になります。生成されたコードをコピーして終わりにせず、第6部で学んだように自分のテーマ・コンポーネント設計へ取り込んで所有する。shadcn/ui の「コピーして所有する」思想(第25章)は、まさに AI 時代のこの作法と地続きです。
実務では、AI の出力を「完成品」ではなく、レビュー対象の下書きとして扱います。まずプロジェクトのテーマトークンを使っているか、動的クラス名を作っていないか、レスポンシブとアクセシビリティが落ちていないかを確認します。そのうえで、長いクラス列は第6部の方法でコンポーネントに取り込み、必要なら CVA や Rails のヘルパーでバリアントを整理します。AI に生成させるほど、むしろ人間側には第26章の規約と第27章のレビュー観点が必要になります。
「フレームワークが薄くなり、ブラウザと AI が賢くなる」未来において、Tailwind は人・機械・ブラウザの三者をつなぐ共通言語として、ますます重要になっていく可能性があります。
参考資料
第31章 Tailwind CSS を選ぶべきか
本書の締めくくりです。ここまでの全内容を踏まえ、最も実務的な問い——「自分のプロジェクトで Tailwind を採用すべきか」——に、判断材料を添えて答えます。第28章で見たとおり、Tailwind は万能ではありません。大切なのは、強みと弱みを理解したうえで、プロジェクトの性質に照らして選ぶことです。
31.1 採用判断のチェックリスト
次の問いに「はい」が多いほど、Tailwind が向いています。
- 中〜大規模で、長く保守されるプロジェクトか?(CSS が線形に増えない利点が効く)
- コンポーネント化の手段があるか?(React/Vue、Rails の partial / ViewComponent など。「醜い HTML」を畳み込める)
- デザインの一貫性をチームで仕組みとして担保したいか?(制約・トークンが効く)
- 独自デザインの UI を素早く作る必要があるか?(既製の UI フレームワークより自由度が要る)
- チームが学習コストの立ち上がり期間を許容できるか?(第28章)
逆に「小さく短命な静的ページ」「コンポーネント化できない環境」「セマンティック CSS で合意済みのチーム」では、慎重になるべきです。
31.2 Rails プロジェクトでの判断軸
Rails では、Tailwind は特に相性の良い選択肢です。
tailwindcss-railsで Node 不要で導入でき(第8章)、Rails らしいシンプルな構成を保てる。- partial → ViewComponent / Phlex というコンポーネント化の道筋が整っており(第24章)、「醜い HTML」を畳み込める。
- Hotwire(Turbo / Stimulus)とも素直に組み合わせられる。
「Rails で素早く、一貫した UI を作りたい」なら、Tailwind は有力な第一候補です。実際、近年の Rails では Tailwind が標準的な選択肢の 1 つになっています。
31.3 React / Next.js プロジェクトでの判断軸
React 系では、Tailwind はエコシステムの中心にあります。
cn・CVA・Headless UI・shadcn/ui という成熟した道具がそろっている(第6部)。- コンポーネント単位で見た目が完結し、JSX との相性が良い。
- Next.js では動的クラス名の回避(第25章)に注意すれば、サーバー/クライアント両方で問題なく使える。
特に shadcn/ui を使うなら Tailwind は実質的に前提です。React で独自デザインのアプリを作るなら、Tailwind は最も無難で強力な選択肢でしょう。
31.4 代替案との比較
Tailwind 以外の選択肢も公平に見ておきます。
| 方式 | 向いている場面 | Tailwind との違い |
|---|---|---|
| 素の CSS + カスタムプロパティ | 小規模、CSS に習熟、制約を自前で設計できる | 一貫性は自力で担保。命名コストは残る |
| CSS Modules | コンポーネント単位でスコープを切りたい | スコープは得られるが、一貫性の仕組みは別途必要 |
| CSS-in-JS | JS と密に連携したい、動的スタイルが多い | 実行時コストや SSR の考慮。一貫性は別途 |
| UnoCSS | Tailwind 的だが、より柔軟・高速にしたい | 思想は近い。コアを持たずプリセットで構成 |
「制約による一貫性 + 必要分だけ生成 + 成熟したエコシステム」をまとめて得たいなら Tailwind、という整理になります。
31.5 段階的導入と撤退可能性
第28章で見たとおり、Tailwind は「採用は容易だが撤退は重い」性質があります。これを踏まえた現実的な進め方は、
- 小さく始める: 新規画面や新規コンポーネントから Tailwind を使い、既存は触るついでに移行(第24章)。
- 撤退の重さを意識して選ぶ: 「このプロジェクトで長く使い続ける見込みがあるか」を導入前に考える。
- トークンに寄せておく: 直値を避けてテーマに集約しておけば(第26章・第27章)、将来の方針変更にも比較的強くなる。
31.6 結論: どんな読者に勧められるか
本書の結論です。Tailwind CSS は、次のような読者に強くおすすめできます。
- Rails や React で、独自デザインのアプリを、チームで長く作っていく人。
- CSS の「大規模化で壊れる」問題(第1章)に、実際に苦しんだことのある人。
- デザインの一貫性を、根性ではなく仕組みで守りたい人。
一方で、Tailwind は「CSS を学ばなくてよくする道具」では決してありません。本書で繰り返し見たとおり、Tailwind を使いこなすには CSS の理解そのものが土台になります(flex も grid も --spacing も、すべて CSS の知識の上に立っています)。Tailwind は CSS の代わりではなく、CSS をチームで一貫して、速く、壊さずに書くための層です。
そして最も大切なことを、最後に。Tailwind は目的ではなく手段です。本書で学んだ思想・仕組み・設計・批判のすべては、「良い UI を、無理なく作り、長く保守する」という目的のためにあります。その目的に照らして、Tailwind が役に立つなら使い、合わないなら別の道を選ぶ——その判断ができるようになったなら、本書の役目は果たせたことになります。
参考資料
演習(第8部)
最後は「自分で判断する」演習です。正解は 1 つではありません。
- 移行を計画する: 既存の v3 プロジェクトを v4 に上げる手順を、§29.6 を見ずにまず書き出し、その後で照らし合わせてください。とくに
shadow-*のリネームとcurrentColor化は何に注意すべきですか。 - 採用判断する: あなたが次に作る(または関わる)プロジェクトを 1 つ思い浮かべ、§31.1 のチェックリストで採点してください。Tailwind を採用すべきか、理由とともに結論を出しましょう。
- 言語化する: 「Tailwind は CSS の代わりではなく、CSS をチームで一貫して書くための層である」(§31.6)という主張に、賛成・反対どちらでもよいので、本書全体を踏まえた自分の意見を 200 字程度で書いてください。
付録
本文を読み終えた読者が、実務で手元に置いて使うための資料です。付録A は公式ドキュメントの歩き方、付録B はよく使うユーティリティ、付録C は早見表、付録D は用語集、付録E は AI に Tailwind コードを依頼するときのプロンプト集、付録F は「ゼロから動く最小例」のハンズオンです。
はじめて Tailwind を動かす人は、付録F から始めても構いません。 Play CDN / Rails / Vite の 3 経路で、コピペして「Hello, Tailwind!」を表示するまでを通しで案内します(第3部からもここへ誘導しています)。
付録A 公式ドキュメントの歩き方
本書は「なぜ」を理解するための教科書であり、すべてのクラスを網羅してはいません。日々の開発では、公式ドキュメント(https://tailwindcss.com/docs)を引くのが基本になります。ここでは、その歩き方を示します。
docs の構成
公式ドキュメントは、おおむね次のように分類されています。
- Getting started / Core concepts: 導入(Installation)と、本書の第2部にあたる中心的な考え方(Styling with utility classes / Hover, focus & other states / Responsive design / Dark mode / Theme / Colors / Adding custom styles / Detecting classes / Functions and directives)。困ったらまずここに戻ります。
- Base styles: Preflight などの土台。
- Layout / Flexbox & Grid / Spacing / Sizing / Typography / Backgrounds / Borders / Effects / Filters / Transitions & Animation / Transforms / Interactivity / SVG / Accessibility: ユーティリティのカテゴリ別リファレンス(本書の第4・5部に対応)。
- Customization 系(Theme / Functions and directives / Detecting classes など): テーマや設定(本書の第5章・第26章に対応)。
効率的な引き方
- 検索を使う: docs 上部の検索(
⌘K/Ctrl K)が最速です。プロパティ名(gap・grid-template-columns)でもクラス名でも引けます。 - 各ページの「Quick reference」表: 各ユーティリティのページ冒頭に、クラスと生成 CSS の対応表があります。値を確認したいときはここを見ます。
- バージョンに注意: 本書は v4 前提です。古い記事や v3 の docs を見ていないか、URL とバージョン表記を確認しましょう。
- Playground と Upgrade guide: 挙動を試すなら Tailwind Play(https://play.tailwindcss.com/)、移行するなら Upgrade guide(https://tailwindcss.com/docs/upgrade-guide)。
付録B よく使う Utility 一覧
実務で登場頻度の高いユーティリティを、カテゴリ別に厳選しました。網羅ではなく「まずこれを覚えれば大半は書ける」というセットです。
Spacing(余白・第10章)
- padding:
p-*px-*py-*pt-* pr-* pb-* pl-* - margin:
m-*mx-*my-*(負:-m-*) - 間隔:
gap-*(Flex/Grid)/space-x-* space-y-*
Sizing(サイズ・第14章)
- 幅高さ:
w-*h-*size-* - 割合・特殊:
w-1/2w-fullw-screenh-dvh - 制限:
min-w-*max-w-*min-h-*max-h-*
Typography(文字・第11章)
- サイズ/行間:
text-xs〜text-9xl、text-sm/6 - 太さ/字間:
font-normalfont-mediumfont-bold/tracking-tight - 揃え/装飾:
text-centertext-right/underline/truncateline-clamp-*
Colors(色・第12章)
bg-*text-*border-*ring-*(例:bg-blue-600text-white)- 不透明度:
bg-black/50
Flexbox(第15章)
flexflex-colflex-wrapjustify-centerjustify-between/items-centergrowshrink-0basis-*
Grid(第16章)
gridgrid-cols-*col-span-*row-span-*gap-*
Borders / Effects(第13章)
borderborder-2/rounded-mdrounded-fullshadow-smshadow-md/ring-2opacity-*blur-*
Layout(第14章)
blockinline-blockhiddenflexgridrelativeabsolutefixedsticky/inset-0top-*z-*overflow-hiddenoverflow-auto/object-cover
States / Variants(第6章)
- 状態:
hover:focus:focus-visible:active:disabled: - レスポンシブ:
sm:md:lg:xl:2xl:(およびmax-*:) - その他:
dark:group-*:peer-*:has-*:aria-*:data-*:motion-reduce:
付録C チートシート
1 ページで引ける早見表です。
ブレークポイント(第17章)
| プレフィックス | 最小幅 |
|---|---|
sm: | 40rem (640px) |
md: | 48rem (768px) |
lg: | 64rem (1024px) |
xl: | 80rem (1280px) |
2xl: | 96rem (1536px) |
※ コンテナクエリは @container + @sm: @md: ...(親要素の幅に反応)
spacing スケール(第10章)
基準 --spacing は既定 0.25rem(4px)。数値 × 0.25rem。
| クラス | 値 |
|---|---|
p-1 | 0.25rem (4px) |
p-2 | 0.5rem (8px) |
p-4 | 1rem (16px) |
p-6 | 1.5rem (24px) |
p-8 | 2rem (32px) |
p-12 | 3rem (48px) |
フォントサイズ(第11章)
| クラス | 値 |
|---|---|
text-xs | 12px |
text-sm | 14px |
text-base | 16px |
text-lg | 18px |
text-xl | 20px |
text-2xl | 24px |
text-3xl | 30px |
色の段階(第12章)
50(最も明るい)→ 100 → … → 500(基準)→ … → 900 → 950(最も暗い)。
例: bg-gray-50 text-gray-500 border-gray-900。不透明度は /数値(例 bg-black/50)。
影(v4・第13章)
小 → 大: shadow-2xs shadow-xs shadow-sm shadow-md shadow-lg shadow-xl shadow-2xl(無: shadow-none)
※ v3 の shadow-sm は v4 の shadow-xs、v3 の shadow は v4 の shadow-sm。
v3 → v4 主な変更(第29章)
| v3 | v4 |
|---|---|
@tailwind base; ... | @import "tailwindcss"; |
tailwind.config.js | @theme(CSS) |
!bg-red-500 | bg-red-500! |
shadow-sm / shadow | shadow-xs / shadow-sm |
rounded-sm | rounded-xs |
ring(3px) | ring-3(既定は 1px) |
outline-none | outline-hidden |
既定のボーダー色 gray-200 | currentColor |
付録D 用語集
本書で登場した重要語をまとめます。詳しくは各章を参照してください。
- ユーティリティクラス: 単一の役割だけを持つ小さなクラス(
p-4・bg-whiteなど)。これを組み合わせてデザインする(第3章)。 - Utility First(ユーティリティファースト): ユーティリティクラスの組み合わせで UI を作る設計思想(第3章)。
- バリアント: ユーティリティに条件を付ける接頭辞(
hover:・md:・dark:など)。CSS のセレクタやメディアクエリを生成する(第6章)。 - 任意の値(arbitrary value): 角かっこでスケール外の値を直接書く記法(
p-[13px])。便利だが乱用は一貫性を崩す(第4章・第27章)。 - テーマ変数 / デザイントークン:
@themeで定義する、色・余白などの「デザイン上の決め事」。CSS 変数とユーティリティ生成を兼ねる(第5章)。 - セマンティックトークン: 役割を表す色名などのトークン(
--color-primaryなど)。ダークモードや配色変更に強い(第12章・第26章)。 @theme: テーマ変数を定義する v4 のディレクティブ(第5章)。@apply: ユーティリティを自前の CSS クラスに展開するディレクティブ。多用は非推奨(第22章)。@utility: 単機能のカスタムユーティリティを追加する v4 のディレクティブ(第22章・第26章)。@plugin: JS ベースのプラグイン(typography/forms など)を CSS から読み込む互換ディレクティブ(第26章)。@source: クラス走査の対象を明示的に追加/除外するディレクティブ(第4章・第26章)。@custom-variant: 独自のバリアントを定義するディレクティブ。手動ダークモードにも使う(第6章・第18章)。- JIT(Just-In-Time): 使われているクラスだけをその場で生成する仕組み(第4章)。
- Oxide: v4 の新エンジンのコードネーム。性能重視部分は Rust 製(第4章・第29章)。
- Lightning CSS: v4 が内蔵する Rust 製の CSS 処理ツール(パース・プレフィックス・圧縮など)(第4章・第29章)。
- カスケードレイヤー(
@layer): レイヤーの優先順位で詳細度の戦いを制御するモダン CSS 機能(第4章)。 - コンテナクエリ(container query): 画面ではなく親要素の幅に反応するレイアウト(
@container/@md:)(第14章・第17章)。 - Preflight: Tailwind が当てる土台のリセット CSS(第4章)。
cn:clsx(条件分岐)とtailwind-merge(衝突解消)を組み合わせた定番ヘルパー(第23章)。- CVA(Class Variance Authority): 型安全にバリアントを管理するライブラリ(第23章)。
- モバイルファースト: 無印が全画面に効き、
md:などで広い画面を上書きする設計(第17章)。 - FOUC: ダークモード初期表示などで起きる一瞬のちらつき(第18章)。
付録E AI に Tailwind コードを依頼するときのプロンプト集
第26章・第27章・第30章で見たとおり、AI は Tailwind を出力しやすい一方、規約を知らないと崩れがちです。ここでは、実務ですぐ使えるプロンプトの型を示します。
E.1 良い依頼の型: 前提を渡す
「カードを作って」だけでは、AI は独自の色やスケールで作ってしまいます。既存のテーマと規約を前提として渡すのがコツです。
悪い例:「Tailwind でカードコンポーネントを作って」
良い例:「Tailwind v4 でカードを作ってください。色は既存のデザイントークン(
bg-surfacetext-foregroundborder-border)だけを使い、独自のbg-gray-*や任意の値([...])は使わないでください。余白は spacing スケール(p-4p-6など)に従ってください。」
E.2 出力形式の明示
「出力は React の関数コンポーネント(TypeScript, tsx)で、props で
intent(primary/secondary)とsize(sm/md)を受けられるようにしてください。バリアントは CVA で定義し、cnヘルパーでclassNameを合成してください。」
Rails なら「ERB の部分テンプレートで」「ViewComponent で」、と形式を指定します。
E.3 制約の指定(崩れを防ぐ)
「次の制約を守ってください。
- クラス名は完全な文字列で書く(
bg-${color}のような動的生成は禁止)。- 色・余白はデザイントークンを使い、任意の値(
[...])は避ける。- レスポンシブ(
sm:md:)とアクセシビリティ(aria-*、focus-visible:、適切な HTML 要素)を考慮する。」
E.4 そのまま使えるプロンプト例
新規 UI 生成:
「Tailwind v4 + React(tsx) で、通知カードのコンポーネントを作ってください。デザイントークン(
bg-surfacetext-foregroundtext-muted)のみ使用。タイトル・本文・閉じるボタンを持ち、閉じるボタンにはaria-labelとfocus-visible:のリングを付けてください。動的クラス名と任意の値は使わないこと。」
既存コンポーネントの改修:
「次のコンポーネントを、ダークモード対応にしてください。
dark:を直接ベタ書きするのではなく、セマンティックトークン(bg-surfaceなど)で表現する方針です。アクセシビリティとレスポンシブは崩さないでください。\n\n(ここにコードを貼る)」
リファクタリング:
「次の長いクラス列を持つボタンを、CVA でバリアント整理し、
cnでclassNameを合成する形にリファクタリングしてください。見た目は変えないこと。\n\n(ここにコードを貼る)」
E.5 レビュー用チェックリスト
AI の出力は「下書き」として受け取り、次を確認してから取り込みます(第27章)。
- 使われているクラスは実在するか(綴り間違いがないか)。
-
レスポンシブが破綻していないか(
md:などの設計が妥当か)。 -
アクセシビリティを落としていないか(適切な要素・
aria-*・フォーカス可視化)。 - 色・余白・角丸・影がトークンに沿っているか(直値・任意値の乱用がないか)。
-
text-${color}のような検出漏れパターンがないか。 - 長いクラス列は、コンポーネントや CVA に畳み込めているか。
付録F ハンズオン: ゼロから動く最小例
本文は概念を順に積み上げる構成のため、「とにかく一度、自分の手で動かして青い文字を出してみたい」という人向けに、ゼロから完成まで通しで動く最小例をここにまとめます。3 つの経路を用意しました。まずは F.1(ビルド不要)で「効く」体験をしてから、F.2(Rails)や F.3(Vite)に進むのがおすすめです。
どの経路でも、最後に画面で 「Hello, Tailwind!」が大きな青い太字+下線で表示されれば成功です。
F.1 いちばん速い: ビルド不要(Play CDN)
エディタも npm も要りません。次の内容で index.html を作り、ブラウザで開くだけです(第7章 Play CDN)。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>
<body class="p-8">
<h1 class="text-3xl font-bold text-blue-600 underline">Hello, Tailwind!</h1>
<button class="mt-4 rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700">
ボタン
</button>
</body>
</html>
ファイルをダブルクリックしてブラウザで開き、次がすべて確認できれば成功です。
-
「Hello, Tailwind!」が青い大きな太字で表示される(
text-3xl font-bold text-blue-600) -
見出しに下線がある(
underline) -
その下に青いボタンがあり、マウスを乗せる(ホバーする)と色が濃くなる(
hover:bg-blue-700)
確認できたら、text-blue-600 を text-red-600 に書き換えて保存し直し、見出しが赤くなることを試してください。「クラスを変える → 結果がすぐ変わる」を体感できます。
Play CDN は開発・学習用です(本番では F.2 や F.3 を使います)。理由は、(1) ページを開くたびにブラウザ上で CSS を生成するため表示が遅い、(2) CDN からの読み込みが必要でオフラインでは動かない、の 2 点です。本番では、あらかじめビルドした CSS を配信します。
さらに手軽に試すなら、ファイルすら作らず Tailwind Play(https://play.tailwindcss.com/)に上記の
<body>内を貼るだけでも確認できます。
F.2 Rails で動かす(本書の重点経路)
Rails 8 以降を前提に、新規アプリで完成まで通します(第8章)。ターミナルで順に実行します。
# 1. Tailwind 入りで新規アプリを作る
rails new hello_tailwind --css tailwind
cd hello_tailwind
# 2. 確認用のページを作る
bin/rails generate controller Home index
config/routes.rb を編集し、アプリのトップページ(/)を表示するルーティングを設定します(root は「ルートディレクトリ」ではなく「アプリのトップ URL」の意味です)。
# config/routes.rb
Rails.application.routes.draw do
root "home#index" # "/" にアクセスしたら HomeController の index を表示
end
ビューにユーティリティを書きます。
<%# app/views/home/index.html.erb %>
<div class="p-8">
<h1 class="text-3xl font-bold text-blue-600 underline">Hello, Tailwind!</h1>
<button class="mt-4 rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700">
ボタン
</button>
</div>
開発サーバーを起動します。bin/dev は Rails サーバーと Tailwind のウォッチを同時に動かします(第8章)。
bin/dev
ブラウザで http://localhost:3000 を開き、F.1 と同じく青い見出し+ホバーで色が変わるボタンが出れば成功です。仕上げに、text-blue-600 を text-red-600 に変えて保存してください。bin/dev を動かしたままなら、ウォッチが効いて自動で赤くなります(ターミナルに再ビルドのログが出ます)。この「保存すると自動で反映される」感覚が、「入力 CSS → ビルド → 配信」の流れ(第8章)を体感する最短ルートです。
F.3 Vite(React)で動かす
# 1. Vite + React プロジェクトを作る
npm create vite@latest hello-tailwind -- --template react
cd hello-tailwind
npm install
# 2. Tailwind を入れる
npm install tailwindcss @tailwindcss/vite
vite.config.js にプラグインを追加します。
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [react(), tailwindcss()],
})
CSS エントリで Tailwind を読み込みます。react テンプレートでは src/index.css が用意されているので、その先頭に書きます(テンプレートによっては src/style.css の場合もあります)。
/* src/index.css */
@import "tailwindcss";
この CSS がエントリ(src/main.jsx)で読み込まれていることを確認してください。react テンプレートでは最初から次の 1 行が入っています。これが無いと何も効きません。
// src/main.jsx(最初から入っている import を確認)
import './index.css'
コンポーネントを書きます。
// src/App.jsx
export default function App() {
return (
<div className="p-8">
<h1 className="text-3xl font-bold text-blue-600 underline">Hello, Tailwind!</h1>
<button className="mt-4 rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700">
ボタン
</button>
</div>
)
}
npm run dev で表示される URL を開き、青い見出しが出れば成功です。JSX では class ではなく className を使う点に注意してください(第8章)。
F.4 うまくいかないときは(症状別の診断)
初学者がつまずくのは、たいてい次のどれかです。症状から原因を切り分けましょう。
- 見出しが太くも青くもならない(何も効かない): ①CSS エントリに
@import "tailwindcss";があるか、②それをページ/アプリが読み込んでいるか(HTML の<link>、Vite ならmain.jsxのimport './index.css')を確認(第7章)。古い記事の@tailwind base; ...は v4 では動きません。確認後はブラウザを強制リロード(Cmd/Ctrl + Shift + R)してキャッシュを消します。 - 保存しても変更が反映されない: ウォッチが動いているか確認。Rails は
bin/dev、Vite はnpm run devのターミナルに再ビルドのログが出ているか見ます。出ていなければ、いったん停止(Ctrl + C)して再起動(第7章)。 - 一部のクラスだけ効かない: そのクラス名を
text-${color}のように動的に組み立てていないか(第4章・第27章)。クラス名はつねに完全な文字列で書きます。 - (Rails)画面が 500 エラー: Rails サーバーのターミナルにエラーが出ています。まずそれを読みます。多いのは
config/routes.rbの設定漏れや構文ミスです。 - 困ったら開発者ツール: ブラウザの検証ツール(
F12)で要素を選び、目的のクラスが付いているか、対応する CSS 規則が生成されているかを確認すると、原因の切り分けが速くなります。
本書は以上です。第1章で見た「CSS が大規模化で壊れる」という問題から始まり、Tailwind がそれにどう答えたのか、内部でどう動き、どう使い、どう設計・運用し、どう評価されるのかを、一貫した「なぜ」とともにたどってきました。あとは、あなた自身のプロジェクトで手を動かすだけです。
おわりに
ここまで読んでいただき、ありがとうございました。
本書は、CSS が大規模化で壊れる理由から始まり、Tailwind CSS がそれにどう答えたのか、内部でどう動くのか、どう使い、どう設計し、どう評価すべきかをたどってきました。
Tailwind CSS は、CSS の代わりではありません。CSS をチームで一貫して書くための層です。便利な道具である一方、採用すればクラス列、コンポーネント設計、テーマ、レビュー観点、撤退コストまで含めて引き受けることになります。
大切なのは、Tailwind CSS を目的にしないことです。
あなたのプロジェクトにとって、UI を速く、壊れにくく、一貫して作る助けになるなら、Tailwind CSS は強力な選択肢です。逆に、素の CSS、CSS Modules、CSS-in-JS、既存のデザインシステムの方が合う場面もあります。
本書が、その判断を自分の言葉でできるようになるための材料になれば幸いです。
FjordBootCamp について
本書は、プログラミングスクール FjordBootCamp(フィヨルドブートキャンプ) の教材として作成されました。
FjordBootCamp は、現役エンジニアが運営する日本語のオンラインプログラミングスクールです。未経験からでも学べる Rails エンジニアコースとフロントエンドエンジニアコースがあり、暗記ではなく「自分で考えて学び続ける力」を育てます。本書で身につけた「なぜそうなっているのかを理解する」姿勢は、Tailwind CSS に限らず、これからの学習すべてで効いてきます。
もっと体系的に、仲間やメンターと一緒に学びたくなったら、ぜひのぞいてみてください。
- 公式サイト: https://bootcamp.fjord.jp/
主要な一次情報
最後に、実務で何度も戻ることになる主要な一次情報を挙げます。