Back to posts

Astro+Tailwind CSS 4の組み立て方(Viteプラグイン前提)

このサイトのリニューアルでは、Astro 5 に Tailwind CSS 4 を組み合わせています。作業を始めて最初に戸惑ったのは、v3 の頃の手順がほぼ役に立たなかったことです。tailwind.config.js を作るつもりで調べ始めたら、そのファイル自体が要らなくなっていた。v4 は統合方法も設定の置き場所も変わっているので、このサイトで組んだ構成を、手順と判断理由つきで残しておきます。

前提として、このサイトの統合は @tailwindcss/vite プラグイン経由です。デザイントークンは CSS 変数側に寄せ、角丸を使わないシャープな見た目を基調にしています。

導入手順は3点だけ

やることは多くありません。

  1. パッケージを入れる

    npm install tailwindcss @tailwindcss/vite
  2. astro.config.mjs の vite.plugins にプラグインを登録する

    // astro.config.mjs
    import { defineConfig } from "astro/config";
    import tailwindcss from "@tailwindcss/vite";
    
    export default defineConfig({
      vite: {
        plugins: [tailwindcss()],
      },
    });
  3. グローバルな 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 v3Tailwind 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サイトのダークモード実装メモにまとめました。

参考