web-styling-tailwind について
Tailwind CSS v4+ を使うフロントエンド開発者向けのパターンガイドです。CSS によるテーマ設定、ユーティリティ作成、v3 からの移行を扱い、v4 のディレクティブやレスポンシブスタイルを解説します。
主な機能
- 01CSS の @theme によるテーマ設定
- 02@utility によるカスタムユーティリティ登録
- 03カスタムバリアントの定義
- 04レスポンシブとコンテナクエリのスタイル
- 05v3 から v4 への移行案内
活用シーン
- 01新規プロジェクトで Tailwind CSS v4 を設定
- 02v3 のプロジェクトを v4 に移行
- 03画面幅やコンテナ幅に応じたレイアウト調整
- 04ダークモードやコンポーネント状態のスタイル
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-styling-tailwindインストール後、Claude Code で/web-styling-tailwindを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.yamlreference.mdexamples/advanced.mdcore.md詳細
Tailwind CSS v4+ のパターン
クイックガイド: v4 では JavaScript ではなく CSS で設定します。
@import "tailwindcss"が 3 つの@tailwindディレクティブに置き換わり、@themeがtailwind.config.jsに置き換わります。@theme内の各変数は、ユーティリティクラスと実際の CSS カスタムプロパティの両方を生成します。カスタムユーティリティは@utilityから、カスタムバリアントは@custom-variantから作成します。v3 のプラグイン API と@layer utilitiesは、バリアントシステムに反映されなくなりました。ソースファイルは自動検出されるため、content配列は不要です。いくつかのデフォルト値も変更されています。ボーダーはcurrentColor、ringは 1px になり、シャドウと角丸のスケールは 1 段階ずれ、重要修飾子は末尾に付きます(flex!)。
詳細リソース:
- examples/core.md — セットアップとソース検出、レスポンシブデザイン、3 つのダークモード戦略、状態バリアント、
@themeによるカスタマイズ - examples/advanced.md —
@utility、@custom-variant、コンテナクエリ、3D 変換、@starting-style、マスク、クラスの合成 - reference.md —
@themeの名前空間テーブル、v3 から v4 への変更点、名称変更されたユーティリティ、ブラウザーのサポート状況
該当する手順
- 新規プロジェクトの場合 — 使用するバンドラー向けのパッケージをインストールし、
@import "tailwindcss"を追加して、すべてのトークンを@themeに記述します。examples/core.md から始めてください。 - v3 プロジェクトをアップグレードする場合 — まず
npx @tailwindcss/upgradeを実行します。これにより、設定、ディレクティブ、名称変更されたユーティリティの大半が変換されます。未変換の項目については reference.md の表を参照してください。また、カスタムプラグインは手動で@utilityと@custom-variantに置き換える必要があります。examples/advanced.md を参照してください。
<critical_requirements>
Tailwind コードを書く前に
CSS で設定します:@import "tailwindcss" と @theme。 v4 は、@config ディレクティブで参照しない限り、tailwind.config.js をまったく探しません。また、3 つの @tailwind ディレクティブは廃止されています。
カスタムユーティリティは @utility で宣言します。 これによりバリアントシステムに登録され、hover: や lg: が適用されます。@layer utilities ブロックで生成されるのは通常のクラスであり、バリアントを前置できません。
バンドラーに合ったパッケージをインストールします — @tailwindcss/vite、@tailwindcss/postcss、@tailwindcss/webpack、または @tailwindcss/cli です。tailwindcss 自体は PostCSS プラグインではなくなりました。また、ベンダープレフィックスの付与とインポートのインライン化は組み込まれているため、autoprefixer と postcss-import は不要です。
@theme の色には oklch() などの広色域の値を指定します。 デフォルトのパレットは oklch なので、16 進数のブランドカラーは sRGB に制限され、隣り合う色と比べて平坦に見えます。
ボーダーとリングの値は明示的に指定します。 v4 では、ボーダーのデフォルトが currentColor、リングのデフォルトが 1px です。そのため、v3 から引き継いだマークアップは、テキストと同じ色のボーダーと、指定より細いリングで表示されます。
</critical_requirements>
自動検出: @import "tailwindcss"、@theme、@theme inline、@utility、@custom-variant、@variant、@source、@reference、@slot、--value()、--modifier()、--spacing()、@tailwindcss/vite、@tailwindcss/postcss、@tailwindcss/cli、tailwind-merge、@container、dark:、group-hover:、peer-checked:、data-[state=…]:
対象:
- マークアップにユーティリティクラスを直接記述するスタイリング
- クラスとカスタムプロパティの両方を生成する、CSS でのテーマトークンの宣言
- ビューポートのブレークポイントとコンテナクエリを使ったレスポンシブレイアウト
- メディアクエリ、クラス、またはデータ属性によるダークモード
- フレームワークに含まれていないユーティリティやバリアントの追加
- v3 プロジェクトから v4 への移行
別の項目で扱う内容:
- テーマエントリーの背後にあるトークン設計 — 値をどのように階層化し、命名し、他のプラットフォーム向けに生成するか。
@themeはトークンを クラス にする場所であり、トークンセットを設計する場所ではありません - テーマの選択、保存、初回描画前の適用方法 —
darkバリアントが決めるのはテーマの見た目であり、どのテーマが有効かではありません - コンポーネントの props をクラスの組み合わせ表に変換する方法 —
cn()はリストを組み立てますが、そのリストをどこで定義するかは別の問題です - スコープ付きの手書きスタイルシート —
@referenceを使うとその内部からテーマを参照できますが、スコープの仕組み自体は別の項目で扱います
ユーティリティファーストであり、v4 では CSS ネイティブでもあります。スタイルはマークアップ内の単一目的のクラスを組み合わせて構成し、それらのクラスに反映されるデザイン上の判断は JavaScript の設定ではなく CSS に記述します。つまり、すべての @theme エントリーが、生成するクラスとともにカスタムプロパティも出力するため、テーマはビルド時だけでなくブラウザーからも読み取れます。
この二重の役割を押さえておくことが重要です。@theme 内の --color-brand-500 は、var() を通じてどのスタイルシートからも参照できるトークンであると同時に、bg-brand-500、text-brand-500 など、その名前空間のクラスの生成元でもあります。v4 の扱いにくい点の多く — inline が存在する理由、@theme をネストできない理由、チャンネル値だけの 3 要素の組が不透明度モディファイアを壊す理由 — は、同じ変数が両方の役割を担うことに由来します。
<decision_framework>
判断の枠組み
ダークモードの方式
Should the theme follow the operating system and nothing else?
|-- YES --> Nothing to configure. `dark:` already means prefers-color-scheme: dark.
|-- NO --> A user-facing toggle is needed. What carries the state?
|-- A class on <html> --> @custom-variant dark (&:where(.dark, .dark *))
|-- A data attribute on <html> --> @custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *))
デザイン上の判断を記述する場所
Should it produce a utility class?
|-- YES --> @theme. Reference another variable? Then @theme inline.
|-- NO --> Is it a whole declaration block reused across components?
|-- YES --> @utility, so variants can still prefix it
|-- NO --> :root, read directly with var() — no class is generated
何に対するレスポンシブ対応か?
The viewport --> sm: md: lg: breakpoint variants
The element's own container --> @container on the parent, then @sm: @md: on the child
Neither --> unprefixed utilities
</decision_framework>
基本パターン
パターン 1:CSS ファーストのセットアップ
インポートは 1 つ、テーマブロックは 1 つ、バンドラー独自のプラグインを使います。content 配列はなく、.gitignore を考慮しながらソースファイルを自動検出します。
@import "tailwindcss";
@theme {
--color-brand-500: oklch(0.7 0.15 250);
--font-display: "Satoshi", "sans-serif";
}
@source は自動検出で見つからないパスを追加します(デフォルトでは node_modules はスキップされます)。@source not はパスを除外し、@source inline("…") は実行時に組み立てられ、スキャンでは見つけられないクラスをセーフリストに追加します。@reference "../app.css" を使うと、出力を重複させずに、スコープ付きスタイルシート内でテーマを利用できます。
全コード:examples/core.md
パターン 2:レスポンシブデザイン
モバイルファーストです。プレフィックスのないユーティリティはあらゆる幅で適用され、各ブレークポイントバリアントがその幅以上で上書きを追加します。カスタムブレークポイントは @theme エントリーで定義します。
<div
class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
></div>
@theme {
--breakpoint-3xl: 120rem;
}
範囲を指定するにはバリアントを重ねます(md:max-lg:hidden)。また、ルールがある幅より上ではなく下で適用される場合は、max-* バリアントを使います。あらゆる場面で max-* を使うのはデスクトップファーストであり、ブレークポイントごとに 1 つではなく、上書きが必要になります。
全コード:examples/core.md
パターン 3:ダークモード
dark: は設定なしで prefers-color-scheme に従います。ユーザーが切り替えられるようにするには、バリアントを上書きして、クラスや属性を参照するようにします。
@import "tailwindcss";
/* Class-based toggle */
@custom-variant dark (&:where(.dark, .dark *));
/* Or attribute-based */
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
広い範囲に適用する場合は、すべてのユーティリティに dark: のペアを用意するより、セマンティックなテーマエントリーを使うのが効果的です。コンポーネントには役割を表す名前を付け、スタイルシート内でモードを一度だけ切り替えます。
@theme inline {
--color-surface: var(--surface);
}
:root {
--surface: oklch(0.99 0 0);
}
.dark {
--surface: oklch(0.15 0 0);
}
全コード:examples/core.md
パターン 4:状態バリアント
疑似クラスバリアントは要素自体にスタイルを適用します。group-* は祖先の状態に応じて子要素に、peer-* は兄弟要素の状態に応じてスタイルを適用し、data-[…] はコンポーネントが設定する属性に応じてスタイルを適用します。
<div class="group rounded-lg p-4 hover:shadow-lg">
<h3 class="text-gray-900 group-hover:text-blue-600">Title</h3>
</div>
<input class="peer" />
<p class="hidden peer-invalid:block">Please enter a valid email</p>
<button data-state="active" class="data-[state=active]:border-b-2">Tab</button>
グループやピアが入れ子になる箇所では名前を付けます(group/card、group-hover/card:)。名前を付けないと、内側のものが外側に反応してしまいます。not-* は条件を反転します。not-hover:opacity-75 を使うと、ベース値にホバー時の上書きを追加する代わりに、条件を反転できます。
全コード:examples/core.md
パターン 5:@theme によるテーマのカスタマイズ
各エントリーは、名前空間内のユーティリティとカスタムプロパティの両方を生成します。--color-*: initial は独自の定義を追加する前に 1 つの名前空間をクリアし、--*: initial はデフォルトテーマ全体をクリアします。
@theme {
--color-brand-500: oklch(0.55 0.2 250);
--font-display: "Satoshi", sans-serif;
--radius-card: 1rem;
--shadow-card: 0 2px 8px oklch(0 0 0 / 0.08);
--ease-fluid: cubic-bezier(0.3, 0, 0, 1);
}
@theme inline は参照先の変数を生成される各ユーティリティに展開します。これにより、.dark や [data-theme] で再割り当てされた値をクラスに反映できます。@theme static は、誰も使用していないユーティリティについてもカスタムプロパティを出力します。名前空間とユーティリティの対応表は reference.md にあります。
全コード:examples/core.md
パターン 6:@utility によるカスタムユーティリティ
@utility はバリアントシステムにユーティリティを登録します。末尾に -* を付けると機能的なユーティリティになり、--value() は受け付ける値(テーマエントリー、ベア値、角括弧で囲まれた任意の値)をカスケード順で決定します。
@utility content-auto {
content-visibility: auto;
}
@utility tab-* {
tab-size: --value(--tab-size-*); /* a theme entry */
tab-size: --value(integer); /* a bare value: tab-4 */
tab-size: --value([integer]); /* arbitrary: tab-[6] */
}
--modifier() は省略可能な /… サフィックスを処理し、モディファイアが省略されると、その宣言全体を省きます。--spacing() はテーマのスペーシング単位を乗算します。
全コード:examples/advanced.md
パターン 7:@custom-variant によるカスタムバリアント
短縮形ではセレクターを指定し、ブロック形式ではルールを指定します。@slot はユーティリティ自身の宣言を配置する場所を示します。
@custom-variant theme-ocean (&:where([data-theme="ocean"] *));
@custom-variant any-hover {
@media (any-hover: hover) {
&:hover {
@slot;
}
}
}
&:where() を使うと、バリアントの詳細度がゼロに保たれるため、詳細度だけでバリアントが優先されることはありません。手書きの CSS 内では、@variant dark { … } を使うと @apply なしで既存のバリアントを適用できます。
全コード:examples/advanced.md
パターン 8:コンテナクエリ
組み込み機能のため、プラグインは不要です。@container は要素をクエリコンテナにし、その子孫に付けた @sm:、@md: はビューポートではなく、そのコンテナの幅に応じて適用されます。
<div class="@container">
<div class="flex flex-col gap-4 @sm:flex-row @lg:gap-6">…</div>
</div>
コンテナが入れ子になる箇所では、コンテナに名前を付けます(@container/sidebar の後に @sm/sidebar:)。@max-* は最大幅をクエリし、@min-md:@max-xl: は範囲をクエリします。
全コード:examples/advanced.md
パターン 9:フレームワークが公開する最新の CSS
v4 では、プラットフォーム機能を通常のユーティリティの背後に配置しているため、あらゆるバリアントと組み合わせられます。
<div class="perspective-distant">
<div
class="transform-3d transition-transform hover:rotate-x-2 hover:rotate-y-3"
>
…
</div>
</div>
<div
popover="auto"
class="transition-all open:opacity-100 starting:open:opacity-0"
>
…
</div>
<textarea class="field-sizing-content"></textarea>
bg-linear-45 と /oklch は角度付きグラデーションと知覚的な補間に対応し、mask-* はグラデーションマスクに、text-shadow-* はテキストシャドウに、pointer-coarse: と pointer-fine: は入力デバイスに応じたコントロールのサイズ調整に対応します。
コード全文:examples/advanced.md
パターン 10:クラスリストの組み合わせ
cn() — 条件付きの結合と競合解決 — を使うと、コンポーネントが公開する className で、コンポーネント自身のユーティリティを実際に上書きできます。
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs));
}
// The caller's className goes last, so it wins the merge
className={cn("rounded-lg px-4 py-2", VARIANT_CLASSES[variant], className)}
これらのユーティリティをマークアップから CSS レイヤーに移し、置き換えたクラスよりも上書きしにくくなる @apply よりも、こちらを優先してください。
コード全文:examples/advanced.md
<red_flags>
注意すべき問題
何も出力されないままになる v3 の構文:
@tailwind base; @tailwind components; @tailwind utilities;— ディレクティブは廃止されています。代わりに使うのは@import "tailwindcss"です。@configを指定していないtailwind.config.js— v4 はこのファイルを探さないため、設定済みに見えても、内容は一切適用されません。@layer utilities { .foo { … } }— クラスは生成されますが、バリアントシステムには認識されないため、hover:fooやlg:fooは存在しません。- PostCSS プラグインとしての
tailwindcss—@tailwindcss/postcssに移行しました。これに伴い、autoprefixerとpostcss-importも不要になりました。 - プラグイン API 経由の
addVariant()—@custom-variantに置き換わりました。 !flexとbg-[--var]— important 修飾子は末尾に付けます(flex!)。また、CSS 変数には括弧を使います(bg-(--var))。
テーマを壊すもの:
- セレクターやメディアクエリー内にネストした
@theme—@themeはトップレベルでのみ使用できます。生成されるクラスはビルド時に出力されるため、実行時のスコープに応じて条件を変えられないからです。モードごとに条件分岐する値は:rootと[data-theme]に記述し、@themeにはエイリアスだけを記述します。 - エイリアスを設定するテーマエントリーで
inlineを省略すること — 生成されるユーティリティには連鎖した参照が含まれ、クラスの使用箇所ではなくテーマエントリーの宣言箇所で解決されます。そのため、[data-theme="dark"]による再割り当てが反映されません。ビルド警告もコンソールメッセージも出ず、ページはライトモードのままです。 - テーマカラーとしての、チャンネル値だけの指定(
0 0% 100%)— 不透明度修飾子はcolor-mix(in oklab, var(--color-x) 50%, transparent)にコンパイルされます。実際の色が必要なため、宣言が削除され、修飾子付きのユーティリティだけが機能しなくなります。 - 同じ値を
:rootと@themeの両方に記述すること — 1つの判断を2か所に複製することになり、値が食い違う可能性があります。不一致は、ユーティリティでスタイルを適用した要素と、直接スタイルを適用した要素との間に微妙な色の差として現れます。 @themeでの16進数カラー — oklch をデフォルトとするパレットの隣で、sRGB にクランプされます。
リグレッションに見えるデフォルト値の変更:
border単体の色はグレーではなくcurrentColorです。色を指定してください。ringは1pxです。v3 のデフォルトはring-3です。- シャドウ、角丸、ぼかしの各スケールは、それぞれ1段階ずれています。v3 の
shadowは v4 のshadow-smに、v3 のshadow-smはshadow-xsに相当します。 outline-noneはoutline-hiddenに変わりました。- バリアントの重ねがけは、カスケードに従って左から右へ読み取られます。v3 とは逆です。
hover:はデバイスが実際にホバーに対応している場合にのみ適用されるため、タッチデバイスではタップ後もホバー状態が維持されることはありません。space-y-*とdivide-*のセレクターが変わりました。より予測しやすいのは、flex または grid とgap-*を組み合わせる方法です。
意外な動作:
- 自動ソース検出では
node_modulesがスキップされるため、インストール済みのコンポーネントライブラリ内のクラスには、明示的な@sourceが必要です。 - 実行時に組み立てられるクラスは検出されません。セーフリストには
@source inline("…")を使用します。 - グリッドのテンプレート値はカンマではなくアンダースコアで区切ります:
grid-cols-[max-content_auto]。 - スコープ付きスタイルシート内で
@applyを使う場合は、先に@referenceが必要です。指定しないとテーマがスコープ内にありません。 - v4 の対応ブラウザーは Safari 16.4 以降、Chrome 111 以降、Firefox 128 以降です。古いエンジンを対象にできるビルドターゲットはありません。
- Sass、Less、Stylus はサポートされていません。v4 自体がプリプロセッサです。