Astro+Tailwind CSS 4の組み立て方(Viteプラグイン前提)
このサイトのリニューアルでは、Astro 5 に Tailwind CSS 4 を組み合わせています。作業を始めて最初に戸惑ったのは、v3 の頃の手順がほぼ役に立たなかったことです。tailwind.config.js を作るつもりで調べ始めたら、そのファイル自体が要らなくなっていた。v4 は統合方法も設定の置き場所も変わっているので、このサイトで組んだ構成を、手順と判断理由つきで残しておきます。
前提として、このサイトの統合は @tailwindcss/vite プラグイン経由です。デザイントークンは CSS 変数側に寄せ、角丸を使わないシャープな見た目を基調にしています。
導入手順は3点だけ
やることは多くありません。
-
パッケージを入れる
npm install tailwindcss @tailwindcss/vite -
astro.config.mjsのvite.pluginsにプラグインを登録する// astro.config.mjs import { defineConfig } from "astro/config"; import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ vite: { plugins: [tailwindcss()], }, }); -
グローバルな CSS ファイルに Tailwind を読み込み、レイアウトから import する
/* src/styles/global.css */ @import "tailwindcss";--- // レイアウト(例:src/layouts/BaseLayout.astro)の frontmatter import "../styles/global.css"; ---
これで .astro ファイルや Markdown の中でユーティリティクラスが使えるようになります。Astro 5.2 以降なら npx astro add tailwind でも同じ構成を自動で作ってくれますが、やっていることは上の3点と変わりません。中身が分かっている方が後で組み替えやすいので、このサイトでは手動で入れました。
つまずきポイント:v3 時代の
@astrojs/tailwindインテグレーションは、v4 では使いません。古い記事の手順をそのまま踏むと、このパッケージと PostCSS の設定が混ざって混乱します。v4 の入り口は Vite プラグインひとつです。
v3 と v4 で何が変わったか
違いを並べるとこうなります。
| 項目 | Tailwind CSS v3 | Tailwind CSS v4 |
|---|---|---|
| Astro との統合 | @astrojs/tailwind インテグレーション+PostCSS | @tailwindcss/vite プラグイン |
| 設定ファイル | tailwind.config.js が必須 | 原則不要。設定は CSS の @theme に書く |
| CSS の入り口 | @tailwind base; など3行のディレクティブ | @import "tailwindcss"; の1行 |
| 走査対象の指定 | config の content 配列に列挙 | 自動検出が基本(追加指定は @source) |
| プラグインの読み込み | config の plugins 配列 | CSS に @plugin と書く |
実感として一番大きいのは、設定の置き場所です。v3 では「デザインの決定は JS の config に、スタイルは CSS に」と分かれていたのが、v4 では CSS ファイル1枚に寄りました。色やフォントを変えたいときに開くファイルが1つで済むのは、個人サイトくらいの規模だと地味に効きます。
トークンは @theme に書く
v4 で色・フォント・余白などのデザイントークンを足す場所は、CSS の @theme ディレクティブです。
@import "tailwindcss";
@theme {
--color-surface: oklch(1 0 0);
--color-ink: oklch(0.24 0.01 260);
--color-accent: oklch(0.55 0.14 250);
--font-sans: "Hiragino Kaku Gothic ProN", "Noto Sans JP", sans-serif;
}
--color-surface と書けば bg-surface や text-surface が、--font-sans と書けば font-sans が使えるようになります。接頭辞(--color-* や --font-* など)がどのユーティリティに対応するかは決まっていて、その対応表がそのまま「このサイトで使えるクラスの一覧」になります。
もうひとつ効いているのが、@theme で定義した変数は通常の CSS 変数として :root にも出力されることです。つまり var(--color-ink) の形で自前の CSS や Astro コンポーネントの <style> からも同じ値を参照できます。このサイトではトークンを CSS 変数側の正本にして、Tailwind のユーティリティはその参照先として使う、という寄せ方をしています。値を直す場所が1か所なので、色の微調整でテンプレート側を触ることがありません。
角丸はトークンごと消している
このサイトは角丸を使わない方針なので、@theme で角丸の名前空間ごと消しています。
@theme {
--radius-*: initial;
}
--radius-* を initial にすると、デフォルトの rounded-* ユーティリティ自体が生成されなくなります。「使わない約束」ではなく「書いても効かない」形にしておく方が、後から自分で見直すときにも、AI にコードを書かせるときにも、方針が漏れません。
Astro のレイアウトとの付き合い方
global.css の import は、共通レイアウトの frontmatter に寄せています。各ページで個別に読み込むと、読み忘れたページだけスタイルが抜ける事故が起きるので、入口は1か所に固定です。
コンポーネント固有のスタイルは、無理にユーティリティで書かず、Astro 標準のスコープ付き <style> と併用しています。レイアウトや余白のような「並び」の指定は Tailwind、コンポーネント内部の細かい見た目はスコープ付き CSS、と分けると、どちらの良さも潰れません。
Markdown 本文だけは少し事情が違います。本文は素の HTML として出力されるので、見出しや段落にクラスは付いていません。ここは Typography プラグインを @plugin "@tailwindcss/typography"; で読み込んで、本文ラッパーに prose 系のクラスを当てる形にしています。プラグインの読み込みも CSS 側に移った、という v4 の作法どおりです。本文中のコードブロックの見せ方や運用については、Markdown記事のコードブロック運用メモで別途まとめました。
ダークモードの下準備だけ先にやる
ダークモードの切り替え自体はまだ入れていませんが、後付けで困らないよう、トークンの設計だけ先に寄せてあります。やっているのは、意味を持つ変数を :root に置き、ダーク側ではその値だけを差し替える形です。
:root {
--surface: oklch(1 0 0);
--ink: oklch(0.24 0.01 260);
}
@media (prefers-color-scheme: dark) {
:root {
--surface: oklch(0.19 0.01 260);
--ink: oklch(0.92 0.01 260);
}
}
@theme inline {
--color-surface: var(--surface);
--color-ink: var(--ink);
}
ポイントは @theme inline です。テーマ変数から別の変数を参照するときは inline を付けないと、変数の解決される場所の都合で期待と違う値になることがある、と公式ドキュメントにも注意があります。inline を付けておくとユーティリティ側が var(--surface) を直接参照するので、:root の値を切り替えるだけで bg-surface の見た目まで追従します。
この形にしておけば、ダークモードを正式に入れるときの作業は「切り替え条件を prefers-color-scheme からクラスやデータ属性に変える」程度で済みます。全クラスに dark: を足し直すような作業にはなりません。下準備としてはいちばん割のいい部分だと思っています。実際の切り替えの実装は、Astroサイトのダークモード実装メモにまとめました。