第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 変数として共有する: 使用するテーマ変数が出力 CSS に残り、通常の CSS からも参照できます。
- 利用できるユーティリティを定義する: 変数の名前空間から、対応するクラスを使えるようにします。実際に出力するクラスは、ソースの走査結果によって決まります。
@theme {
--color-mint-500: oklch(0.72 0.11 178);
}
この定義で bg-mint-500 や text-mint-500 が使えるようになります。ソースで次の背景色クラスだけを使った場合を考えましょう。
<div class="bg-mint-500">...</div>
/* 生成結果(レイヤーなどは簡略化) */
:root {
--color-mint-500: oklch(0.72 0.11 178);
}
.bg-mint-500 { background-color: var(--color-mint-500); }
未使用の text-mint-500 は生成されません。また、通常の @theme では使われない変数も省略されます。定義することと、出力されることは別です。
出力された変数は、style="background-color: var(--color-mint-500)" のように直接参照することもできます。JavaScript からだけ読むなど、使用がビルド時に分からない場合の扱いは 5.9 で説明します。
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 は、CSS 側で @config "../tailwind.config.js"; と指定して読み込めます。ただし完全な互換ではなく、旧 corePlugins・safelist・separator はサポートされません。生成対象を明示する旧 safelist の用途には、たとえば @source inline("bg-red-500 text-white"); を使います。@config のパスは CSS ファイルからの相対パスです(移行全体は第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(ルート文字サイズ 16px なら 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 変数として出力されます。JavaScript の getComputedStyle() だけで参照する値は、通常の @theme では出力が保証されません。必ず出力したい変数には @theme static を使います。ユーティリティの定義が不要なら、通常の :root に書く方法もあります。
@theme static {
--color-brand: oklch(0.45 0.24 264);
}
static は変数を出力する指定であり、未使用のユーティリティまで全部生成する指定ではありません。公式の変数出力仕様に沿って、用途に応じて選びます。
- 素の CSS との連携: ユーティリティで書きにくい複雑なスタイルを CSS で書くとき、
var(--color-brand)でテーマの色を参照できます。色の定義が二重管理になりません。 - JavaScript からの参照: 実行時に
getComputedStyle(document.documentElement).getPropertyValue('--color-brand')のように、テーマの値を JavaScript から読めます。チャートライブラリに Tailwind の色を渡す、といった用途で便利です。 - 動的なテーマ切り替え: CSS 変数なので、
.darkクラスが付いたときに変数の値を差し替える、といったことも自然にできます(ダークモードは第18章)。
「デザイントークンが、Tailwind の中だけに閉じず、プロジェクト全体で使える共通言語になる」——これが v4 のテーマシステムが目指したゴールです。