Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第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 つの役割を同時に果たします。

  1. CSS 変数として共有する: 使用するテーマ変数が出力 CSS に残り、通常の CSS からも参照できます。
  2. 利用できるユーティリティを定義する: 変数の名前空間から、対応するクラスを使えるようにします。実際に出力するクラスは、ソースの走査結果によって決まります。
@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 で説明します。

bg-mint-500 を使用すると、テーマ変数と対応する背景色ユーティリティが出力される図。
図 5-1 テーマは利用可能なクラスを定義し、使用状況に応じて変数とクラスが出力される。

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 のテーマシステムが目指したゴールです。

参考資料