
特に問題になるのが、theme.jsonとカスタムCSSの役割を同じものとして扱う設計である。theme.jsonは単なる設定ファイルではない。Global Stylesを通じて、テーマの設定値、CSS変数、ブロック単位のスタイルを生成する仕組みである。一方、カスタムCSSはセレクタ、詳細度、読み込み順に基づいてブラウザ上で評価される。
この2つは連携する。しかし、同じ優先順位で動作するわけではない。
WordPressにおけるtheme.jsonとCSSの優先順位を理解するには、まずGlobal Stylesの階層を分けて考える必要がある。そのうえで、生成されるCSS変数、:where()による低詳細度、ブロックのインラインスタイルを確認する。ここを曖昧にすると、修正のたびに!importantが増える。
Global StylesはCore・Theme・Userの3階層で評価される
theme.jsonのスタイル設計で最初に押さえるべきなのは、設定の適用階層である。Global Stylesは大きく次の3段階に分かれる。
| 階層 | 内容 | 優先度 |
|---|---|---|
| Core | WordPress本体やコアブロックが持つ標準設定 | 低い |
| Theme | テーマのtheme.jsonで定義した設定とスタイル | 中間 |
| User | サイトエディターで管理者が変更した設定 | 高い |
基本的な順序は、Core → Theme → Userである。テーマのtheme.jsonはWordPress本体の標準値を上書きできる。しかし、サイトエディター上でユーザーが設定した値は、テーマの初期値より優先される。
これはブロックテーマにおける設計上の前提である。テーマ側で余白や色を固定したつもりでも、ユーザーがサイトエディターから変更できる設定として公開していれば、その値はUser階層に移る。
例えば、テーマ側で段落の文字色を指定しているとする。サイトエディターで段落ブロックの色が変更された場合、テーマのCSSを後から強く書いても、ブロックの設定によって生成されたスタイルが優先されることがある。
ここで「CSSファイルの読み込み順だけを変える」という対策は不十分である。問題が読み込み順ではなく、User階層の設定やインラインスタイルにあるためである。
theme.jsonの設定とスタイルは別の責務である
theme.jsonには、大きく分けてsettingsとstylesがある。
settingsは、サイトエディターで利用できる機能やプリセットを定義する。カラーパレット、フォントサイズ、レイアウト幅、余白設定の可否などが該当する。
stylesは、ルート要素やブロックに対して初期スタイルを定義する。例えば、本文の文字色、背景色、見出しのタイポグラフィ、ボタンの角丸などである。
この2つを混同すると設計が崩れる。
settings.color.paletteで色を登録しただけでは、本文の色がその色になるわけではない。これは利用可能なプリセットを定義する処理である。実際の配色を決めるには、styles.color.textやブロック単位のスタイル設定が必要になる。
同様に、フォントサイズのプリセットを登録しても、各要素に自動で適用されるわけではない。プリセットは選択肢であり、スタイルは適用値である。
この違いを理解せずに「theme.jsonを書いたのにCSSが変わらない」と判断するケースがある。実際には、設定値を登録しただけで、スタイル値を定義していない可能性が高い。
theme.jsonはCSSの代替ではない。テーマの設定、設計上の初期値、ユーザーが変更できる範囲を宣言する仕組みである。Global StylesのCSS詳細度は意図的に低く設計されている
WordPressのGlobal Stylesが生成するCSSには、一般的なテーマCSSとは異なる特徴がある。CSS詳細度を低く保つ設計である。
Global Stylesでは、セレクタに:where()が使われる構成がある。:where()の中に記述されたセレクタは、詳細度がゼロとして評価される。
例えば、通常の.wp-block-buttonはクラスセレクタとして詳細度を持つ。一方、:where(.wp-block-button)は、見た目の対象は同じでも詳細度が非常に低い。
詳細度だけを比較すると、次のようになる。
| セレクタ | 詳細度の考え方 | 備考 |
|---|---|---|
:where(.wp-block-button) | 0 | Global Stylesで使われることがある |
.wp-block-button | クラス1個分 | 通常のテーマCSS |
.entry-content .wp-block-button | クラス2個分 | より強いテーマCSS |
#main .wp-block-button | IDを含む | 過度に強くなりやすい |
| インラインスタイル | 通常のCSSより強い | ブロック設定などで出力される |
この仕様は、テーマ開発者にとって不便に見える。しかし、目的は明確である。Global Stylesを強制的なCSSとして扱わず、テーマやユーザーが必要に応じて上書きできる初期値として機能させるためである。
従来のテーマでは、テーマCSSが強すぎて、プラグインや子テーマで上書きできない問題が頻発した。ブロックテーマでは、Global Stylesの詳細度を低くすることで、拡張性を確保している。
詳細度を上げれば解決するとは限らない
theme.jsonのCSSが効かない場合、まず.wp-block-*のようなクラスセレクタを追加する方法がある。これは一定の範囲では有効である。
しかし、相手がインラインスタイルの場合は別である。
ブロックエディターやサイトエディターで設定された余白、色、フォントサイズなどは、ブロックのHTMLにインラインスタイルとして出力される場合がある。外部CSSの詳細度を上げても、通常はインラインスタイルに勝てない。
例えば、次のようなスタイルがブロック側に出力されているとする。
style="margin-top: 0px;"
この値を外部CSSから変更するには、通常のクラスセレクタでは足りない。!importantが必要になる可能性がある。しかし、!importantを増やすと、サイトエディターでの変更や将来のテーマ更新を妨げる。
問題はCSSの強さではない。どの層が値を管理すべきかが決まっていないことである。
CSS変数を直接の値ではなく設計基盤として使う
theme.jsonのプリセットは、CSSカスタムプロパティとして出力される。色やフォントサイズを登録すると、--wp--preset--*という接頭辞のCSS変数が生成される。
カスタム値については、--wp--custom--*という接頭辞が使われる。
例えば、カラーパレットに登録した値は、概念的には次のようなCSS変数として利用できる。
var(--wp--preset--color--primary)
実際の変数名は、theme.json内のスラッグから生成される。日本語や記号を含むスラッグは扱いにくくなるため、プリセット名には英数字とハイフンを使う設計が安定する。
プリセットを直接ハードコードしない
次の2つの書き方を比較する。
color: #1a1a1a;
color: var(--wp--preset--color--foreground);
前者は単純である。しかし、サイトエディターで配色を変更した場合に連動しない。後者は、テーマの設計したプリセットに接続される。
CSS変数を使うと、色の定義を一箇所に集約できる。テーマの配色変更も、変数の生成元であるtheme.jsonに戻って行える。
ただし、すべての値をCSS変数にすればよいわけではない。コンポーネント固有の状態、複雑なメディアクエリ、アニメーション、疑似要素の細かな制御は、通常のCSSで記述した方が読みやすい。
適切な境界は次のように考えられる。
- デザインシステム全体で共有する色は、
theme.jsonのプリセットで管理する - サイト全体で共有するフォントサイズは、プリセットと
stylesで管理する - コンテンツ幅や余白の基本値は、
theme.jsonで管理する - ホバー、フォーカス、疑似要素などの状態は、カスタムCSSで管理する
- JavaScriptと連動する表示状態は、クラスとカスタムCSSで管理する
- 特定のブロックだけに必要な細部は、ブロック単位のCSSで管理する
この分割を行うと、同じ値をtheme.jsonとstyle.cssに重複して書く必要がなくなる。
CSS変数を上書きする場合は生成元を確認する
CSS変数を使っている場合、見た目のCSSだけを探しても原因は見つからない。
例えば、ボタンの背景色にvar(--wp--preset--color--primary)が使われている場合、色を変える方法は2つある。
1つ目は、theme.jsonのプリセット値を変更する方法である。サイト全体の主色を変更したい場合はこちらが適している。
2つ目は、ボタンのスタイル側で別の変数や固定値を指定する方法である。特定のブロックだけを変更したい場合はこちらを使う。
前者は変更範囲が広い。後者は局所的である。どちらを選ぶかは、値の責務で決まる。
サイト全体の主色を、ボタン専用CSSの中で直接上書きするのは設計として不自然である。逆に、特定のCTAだけの背景色をグローバルなプリセット変更で対応すると、他のブロックまで影響する。
CSS変数は便利だが、依存関係を隠す。変数名だけを見て判断せず、どこで生成され、どのブロックが参照しているかを確認する必要がある。
style.cssは不要にならない
ブロックテーマではtheme.jsonが中心になる。しかし、style.cssが不要になるわけではない。
テーマをWordPress管理画面で認識させるには、テーマ直下のstyle.cssが必要である。テーマ名やバージョンなどのヘッダー情報も、このファイルに記述する。
また、theme.jsonはCSSの全機能を置き換えるものではない。
例えば、次の処理はカスタムCSSの方が適している。
- 複雑なレスポンシブ対応
- 疑似要素の装飾
:hoverや:focus-visibleの状態制御- 特定のHTML構造に依存するレイアウト
- CSSアニメーション
- フォーム要素のブラウザ差異の調整
- プラグインが生成するHTMLへの補正
- ブロックの組み合わせに依存する例外処理
theme.jsonは、サイトのデザイン設定とGlobal Stylesの初期値を管理する。style.cssは、テーマ固有の表現と、CSSとして記述した方が明確なロジックを管理する。
この役割分担を崩すと、theme.jsonが肥大化する。逆に、すべてをstyle.cssに寄せると、サイトエディターの設定とCSSの責務が分離する。
add_theme_support()との重複に注意する
theme.jsonを導入したブロックテーマでは、従来のadd_theme_support()で設定していた機能の一部が、theme.jsonの設定によって上書き、または代替される。
旧来のテーマ設定を残したままtheme.jsonを追加すると、同じ機能を2箇所で管理することになる。設定値の読み手が、どちらを正とすべきか判断できない。
例えば、エディター上での色設定やフォントサイズ設定をfunctions.phpとtheme.jsonの両方に記述する構成は避けるべきである。ブロックテーマの設定は、可能な範囲でtheme.jsonに集約する方が追跡しやすい。
ただし、すべての既存設定を一度に削除する必要はない。使用しているWordPressのバージョン、テーマ構造、依存プラグインの対応状況を確認しながら、責務を移行する。
theme.jsonにはバージョンがある。WordPress 5.9以降ではversion 2が標準的に使われ、WordPress 6.6ではversion 3が導入された。利用する機能が対象バージョンで利用可能かを確認してから、ファイルのバージョンを決めるべきである。
theme.jsonとカスタムCSSを併用する設計
併用の基本は、同じ対象に対する重複指定を減らすことである。
例えば、本文の基本文字色をtheme.jsonで定義し、記事内リンクのホバー色をstyle.cssで定義する。この構成なら、通常状態と状態変化の責務が分かれる。
一方、本文の色をtheme.json、.entry-contentの色をstyle.css、さらにブロック設定のインラインスタイルで指定すると、同じプロパティに3つの管理者が存在する。
実装前に、対象プロパティの管理者を決めるとよい。
| プロパティ | 主な管理場所 | CSSで補う範囲 |
|---|---|---|
| サイト全体の背景色 | theme.json | 特殊なテンプレートのみ |
| 本文の文字色 | theme.json | 状態別の色 |
| ブランドカラー | theme.jsonのプリセット | 特殊コンポーネント |
| コンテンツ幅 | theme.jsonのレイアウト設定 | 特定テンプレートの例外 |
| ボタンの基本色 | theme.jsonまたはブロックスタイル | ホバー、フォーカス |
| カードの影 | カスタムCSS | 複雑な状態制御 |
| 疑似要素の装飾 | カスタムCSS | theme.jsonの対象外 |
| アニメーション | カスタムCSS | アクセシビリティ対応を含む |
theme.jsonに向くものは、サイト全体の設計値である。カスタムCSSに向くものは、状態、構造、動作に依存する表現である。
ブロックスタイルは例外を減らすために使う
特定のブロックだけに別の見た目を与える場合、汎用セレクタで無理に上書きするより、ブロックスタイルを定義した方が保守しやすい。
例えば、通常のボタンとアウトラインボタンを分ける場合、全ボタンに対して!importantを適用するのではなく、ブロックスタイルとして別のクラスを与える構成がよい。
ブロックスタイルは、エディター上で選択可能な選択肢として設計できる。ユーザーが変更する可能性があるデザインなら、サイトエディターの操作対象として公開した方が、CSSで強制するより自然である。
逆に、ブランド上変更させたくない部分をサイトエディターの設定として開放すると、ThemeとUserの境界が崩れる。設定可能な範囲は、運用要件に応じて絞る必要がある。
ブロックエディターでCSSが効かない時の調査手順
CSSが効かない問題は、書き直す前にブラウザの開発者ツールで確認する。推測でセレクタを追加すると、原因を隠したまま詳細度だけが上がる。
調査は次の順番で行う。
1. 対象要素を特定する
エディター上の見た目と、フロントエンドのHTMLは一致しない場合がある。まず、実際にスタイルが適用されている要素を確認する。親要素に指定された値が継承されているのか、対象要素に直接指定されているのかを分ける。
2. 該当プロパティの出力元を確認する
color、margin、padding、font-sizeなど、問題のプロパティを開発者ツールで確認する。適用中のルール、打ち消されたルール、インラインスタイルを分けて見る。
3. CSS変数の解決値を確認する
var(--wp--preset--*)やvar(--wp--custom--*)が使われている場合、変数の定義元を確認する。値が存在しない場合と、値は存在するが別のルールに負けている場合は、対処が異なる。
4. 詳細度を比較する
セレクタの長さではなく、詳細度を比較する。.content .wp-block-headingが、単純な.wp-block-headingより強いのは明らかである。しかし、:where()を含むGlobal Stylesとの比較では、見た目の複雑さだけで判断できない。
5. インラインスタイルを確認する
ブロック設定で指定された値がHTMLのstyle属性に出ていれば、外部CSSとの競合が発生している。ここでセレクタを増やす前に、サイトエディター側の設定を削除または変更すべきか判断する。
6. 読み込み順を確認する
テーマのCSS、ブロック固有CSS、プラグインCSSの読み込み順を確認する。プラグインのCSSは、エンキューの順序やセレクタの詳細度によって結果が変わる。一律の優先順位は存在しない。
7. キャッシュを分離する
ブラウザキャッシュ、ページキャッシュ、CSS最適化機能がある場合、修正が反映されていない可能性がある。開発者ツールで実際に読み込まれたCSSの内容を確認する。キャッシュ削除だけで解決した場合、CSSの優先順位問題とは分けて扱う。
8. エディターとフロントエンドを個別に確認する
エディター用のCSSとフロントエンド用のCSSは、同じファイルとは限らない。編集画面だけで効かないのか、公開画面だけで効かないのかを切り分ける必要がある。
この順序なら、詳細度を無制限に上げずに原因を特定できる。
「効かない」と「別の値になっている」は違う
CSSの調査では、ルールが存在しないケースと、ルールが打ち消されているケースを分ける必要がある。
ルールが存在しない場合は、CSSファイルが読み込まれていない、セレクタがHTMLに一致していない、対象ブロックのマークアップが想定と異なると推測される。
ルールが打ち消されている場合は、詳細度、読み込み順、インラインスタイル、!importantの有無を確認する。
また、値が適用されているのに見た目が変わらないこともある。例えば、子要素が別の色を直接持っている場合、親要素のcolorを変更しても継承されない。余白も同様である。親のpaddingと子のmarginが同時に存在していれば、変更した値が視覚的な差として現れにくい。
プロパティごとに計算値を見る必要がある。ソースコードだけを読んで判断する方法には限界がある。
CSSの競合は、セレクタを書く問題ではない。どの層が値を所有するかを決める問題である。
!importantは最後の補正に限定する
!importantは、競合を強制的に解決するための機能である。使うこと自体が誤りではない。ただし、Global Stylesとインラインスタイルの問題をすべて!importantで処理すると、設定の依存関係が見えなくなる。
特に避けたいのは、同じプロパティに複数の!importantを重ねる構成である。
例えば、次のような状態になると、後からの変更が難しくなる。
theme.jsonで背景色を定義するstyle.cssで背景色を上書きする- プラグインCSSが別の背景色を指定する
- ブロックのインラインスタイルがさらに値を指定する
- 最終的に
!importantで固定する
この時点で、色の変更は単純な設定変更ではない。複数の依存関係を追跡する作業になる。
!importantを使う余地があるのは、主に次のような場合である。
- プラグインが生成するHTMLをテーマ側で安全に補正する
- 管理者が変更できない固定表示を実装する
- 外部CSSの詳細度を変更できない
- アクセシビリティ上、特定の状態を常に優先する必要がある
- 印刷用や特殊メディア向けに値を強制する
ただし、ブロックのユーザー設定を無視する目的で使う場合は、運用上の合意が必要である。サイトエディターで変更できる設計と、CSSで固定する設計は両立しない。
カスタムCSSのセレクタを過剰に強くしない
Global Stylesを上書きするために、IDセレクタや過度に長い子孫セレクタを使う方法がある。短期的には効く。しかし、再利用性が低下する。
ブロックテーマでは、同じブロックが投稿本文、テンプレートパーツ、クエリーループ、ウィジェット相当の領域など、複数の場所に配置される。特定の親構造に依存したセレクタは、配置場所が変わるだけで機能しなくなる。
例えば、.site-main .content-area .wp-block-buttonのようなセレクタは、その構造が保証されている場合にのみ有効である。テンプレートを変更した時点で対象外になる可能性がある。
基本は、対象ブロックのクラスを基準にする。
- ブロック全体の共通スタイルは、ブロッククラスで指定する
- バリエーションは専用クラスで分ける
- 状態は疑似クラスで表現する
- 構造に依存する場合だけ、親子関係を追加する
- IDセレクタは原則として使わない
- 同じプロパティに対する上書きを最小化する
詳細度は、強ければよいものではない。必要な範囲で低く保つ方が、テーマの拡張性を維持できる。
ブロックテーマのCSS設計を分割する
実際のプロジェクトでは、すべてのCSSをstyle.cssに集約すると管理しにくい。テーマの規模が大きくなるほど、役割ごとに分割した方がよい。
一例として、次のような分類が考えられる。
theme.json:デザイン設定、プリセット、Global Stylesstyle.css:テーマヘッダーと全体に必要な共通CSS- ブロック別CSS:特定ブロックの表示調整
- レイアウトCSS:テンプレート構造に依存するレイアウト
- 状態CSS:ホバー、フォーカス、展開状態
- 管理画面用CSS:エディター上だけに必要な補正
ファイルを分ける目的は、ファイル数を増やすことではない。値の所有者を明確にすることである。
CSSを分割しても、同じプロパティを複数のファイルで指定していれば競合は残る。分割は責務の整理とセットで行う必要がある。
また、プラグイン用のCSSをテーマの共通CSSに混在させる設計も避けた方がよい。プラグインが無効になった時に不要なCSSが残り、別のプラグイン導入時にセレクタが衝突する可能性がある。
バージョン差を無視しない
theme.jsonはWordPressのバージョンとともに仕様が変化している。
2021年7月のWordPress 5.8でtheme.jsonとGlobal Stylesの初期機能が導入された。その後、2022年1月のWordPress 5.9でフルサイト編集対応とversion 2が導入された。2024年7月のWordPress 6.6ではversion 3が対応している。
同じtheme.jsonでも、利用しているWordPressのバージョンによって利用可能な設定や出力結果が異なる場合がある。
開発環境と本番環境でWordPressのバージョンが違う場合、次の問題が発生する。
- 開発環境で認識された設定が本番で無視される
- 生成されるCSS変数の有無が異なる
- ブロックのスタイル出力が異なる
- サイトエディターのUIに表示される項目が違う
- 既存の
add_theme_support()との優先関係が変わる
ブロックテーマの開発では、PHPやプラグインだけでなく、WordPress本体のバージョンも依存関係として管理するべきである。
theme.jsonのバージョンを新しくすることが、常に改善とは限らない。対象サイトの更新方針と、運用中のWordPress環境を基準に決める必要がある。
実装前に決めるべき優先順位
スタイルの競合は、実装後に修正するより、実装前にルールを決めた方が安定する。
最低限、次の項目を設計書またはテーマ内のドキュメントに残すとよい。
- サイト全体の色を管理する場所
- フォントサイズのプリセットを管理する場所
- コンテンツ幅とブロック幅の基準
- ユーザーがサイトエディターで変更できる範囲
- 固定する必要があるブランド要素
- プラグインCSSとの境界
!importantを許可する箇所- エディターとフロントエンドで異なるCSS
- WordPressの対応バージョン
- CSS変数の命名規則
特に、ユーザー設定をどこまで許可するかは重要である。
サイトエディターで色や余白を変更できる設計にするなら、テーマCSSはその変更を妨げない方がよい。反対に、ブランドカラーやレイアウトを固定する場合は、UI上で変更可能な設定を公開しない方が一貫する。
変更を許可しながらCSSで上書きする構成は、利用者にとって不具合に見える。設定画面で選択した値が反映されないためである。
破綻しないための確認ポイント
最後に、実装とレビューで確認すべきポイントをまとめる。
1. 同じプロパティを複数の管理層で指定していないか
theme.json、テーマCSS、ブロックスタイル、プラグインCSSに同じ値がある場合、どれを正とするかを決める。重複が必要な場合は、理由を残す。
2. プリセットと適用スタイルを混同していないか
settingsで登録した色やフォントサイズは、利用可能な値である。実際の表示値はstylesやブロック設定で別途決まる。
3. CSS変数の生成元を把握しているか
--wp--preset--*や--wp--custom--*を使う場合、theme.jsonのどの設定から生成されているか確認する。固定値との混在は、変更経路を複雑にする。
4. User階層をCSSで無理に上書きしていないか
サイトエディターで設定された値は、テーマの初期値より優先される。ユーザー設定を尊重するのか、固定するのかを設計段階で決める。
5. インラインスタイルを見落としていないか
外部CSSの詳細度を上げても、インラインスタイルには勝てない場合がある。まずHTMLと計算済みスタイルを確認する。
6. セレクタがテーマ構造に依存しすぎていないか
テンプレートやテンプレートパーツの変更で壊れるセレクタは、保守コストが高い。ブロック単位のクラスとバリエーションを優先する。
7. !importantの使用理由が説明できるか
理由が「CSSが効かなかったから」だけなら、詳細度、読み込み順、インラインスタイル、設定階層を再確認する。
8. エディターとフロントエンドの両方で確認したか
片方だけで表示が正しくても、もう片方で崩れることがある。公開画面と編集画面は別の実行環境として扱う。
まとめ
WordPressのブロックテーマにおけるtheme.jsonとカスタムCSSの競合は、CSSの記述量では解決しない。Core・Theme・Userの3階層、CSS詳細度、インラインスタイル、CSS変数、読み込み順を分けて評価する必要がある。
Global Stylesは、意図的に低い詳細度で出力される。これは上書きしやすくするための設計であり、欠陥ではない。問題は、低詳細度のルールに対して無制限に強いCSSを追加することではない。テーマの初期値、ユーザー設定、局所的な表現の責務を分けることである。
- サイト全体の設定値は
theme.jsonに集約する - 色やフォントサイズはプリセットとCSS変数を基準にする
- 状態、疑似要素、複雑なレスポンシブ処理はカスタムCSSで扱う
style.cssは必須であり、theme.jsonによって不要にはならない- インラインスタイルとUser設定は、単純な詳細度競争で処理しない
!importantは原因を確認した後の限定的な補正にする- WordPress本体のバージョンを依存関係として管理する
スタイルの優先順位を制御する最も確実な方法は、強いセレクタを書くことではない。値の所有者を一つに決め、他の層が担当する範囲を明確にすることである。
Related reading: WordPressとヘッドレスCMSの分岐点:個人開発で選ぶべき設計と移行の実態 and Gutenbergブロックがデータベースに保存され描画される内部構造.