CSS `if()` とコンテナスタイルクエリの使い分け
CSS if() と container style queries の使い分け、構文の落とし穴、range syntax、2026年のブラウザ対応を比較解説。
単一の要素がインラインで自身の値を決定する場合(テーマトークン、コンポーネントの状態、レスポンシブなプロパティの調整など)は、CSS の if() 関数を使用します。一方、ある親要素のスタイルが複数の子孫にわたるルールセットを制御すべき場合は、コンテナスタイルクエリ(@container style(...))を使用します。一言でまとめると、if() は要素ローカルな条件付き値を生成し、コンテナスタイルクエリはコンテキストを確立して条件付きルールブロックを親から子へ適用します。
どちらもカスタムプロパティを読み取る条件付き CSS の仕組みであり、同じ style() クエリプリミティブを使用し、機能が十分に重複しているため、誤った選択をすると不自然なコードになります。本記事では、この重複部分を整理します。各機能の役割、使い分けを決める仕組みの違い、よくある 2 つの構文の落とし穴(セミコロンとコロン対イコールの記法)、両方に導入されたレンジ構文、シナリオ別のツール選択表、そして 2026 年半ば時点でブラウザサポートが三分されている状況での実装方法について解説します。
重要なポイント
if()は同じ要素の同じルール内で宣言されたカスタムプロパティを読み取り、1 つの値を返します。@container style()はカスケードを上方向にのみ参照し、子孫に対してルールブロック全体を適用します。if()内では、条件と値のペアをコンマではなくセミコロンで区切り、elseでフォールバックを指定します。条件が一致せずelseも省略した場合、関数は「確実に無効な値」を返し、プロパティの初期値に解決されます。style()内では、コロン記法(style(--n: 3))は数値計算なしのトークン/文字列マッチングを行い、比較演算子記法(style(--n = 3))は両辺を数値型として解析し、calc()を先に評価してから数値比較を行います。- 2026 年半ば時点でサポート状況は異なります。基本的なコンテナスタイルクエリは主要 4 エンジンすべてで利用可能(Chrome 111+、Edge 111+、Safari 18+、Firefox 151+)ですが、
if()とデフォルト有効のレンジ構文は Chromium のみです。 if()は Chromium のみのため、すべてのif()宣言には同じプロパティのフォールバックを前に置くか、@supportsガードで囲む必要があります。
各機能の最小限の説明
if() 関数は、任意のプロパティ内に配置する値レベルの条件式です。引数はセミコロン区切りの条件と値のペアのリストで、各条件はコロンで値と区切られ、オプションの else でデフォルト値を指定します。
.badge {
color: if(
style(--variant: danger): white;
style(--variant: muted): #555;
else: black;
);
}
各条件は以下の 3 種類のクエリタイプのいずれかです。
style()は現在の要素のカスタムプロパティをテストしますmedia()はインラインメディアクエリを実行しますsupports()は機能クエリを実行します
MDN のリファレンスでは、同じセミコロン文法で 3 つすべてが文書化されています。例えば、flex-direction: if(media(orientation: landscape): row; else: column;) はメディアの結果に基づいて 1 つのプロパティを切り替えます。
コンテナスタイルクエリは、@container アットルールのうち、条件として包含要素のカスタムプロパティを読み取る 1 つ以上の style() 記法を使用するものです。単一の値を生成するのではなく、ルールのブロックをラップします。
.card { --status: active; }
@container style(--status: active) {
.card-title { font-weight: 700; }
.card-icon { opacity: 1; }
.card-meta { color: green; }
}
MDN のコンテナクエリガイドによると、すべての要素はデフォルトでスタイルコンテナです。サイズクエリとは異なり、スタイルクエリでは container-type の設定は不要です。これにより、コンテナクエリに対して多くの人が想定するセットアップの手間がなくなります。
使い分けを決める核心的な違い
Discover how at OpenReplay.com.
仕組みの違いは参照のスコープにあります。if() は同じ要素の同じルール内で宣言されたカスタムプロパティを読み取り、自己完結しています。一方、@container style() はカスケードを上方向にのみ参照して祖先コンテナを探します。MDN はこれを明確に述べています。if() 内でスタイルクエリを使用すると、親のスタイルを確認するのではなく、カスタムプロパティが要素自身に設定されているかどうかに基づいてその要素をターゲットにできます。この 2 つは明示的に補完的なものとして位置づけられており、冗長ではありません。
この参照スコープの違いには 3 つの実際的な影響があります。
-
出力の粒度。
if()は正確に 1 つの値を設定します。@containerは条件が真のとき、複数のセレクターにまたがる複数の宣言を含むルールブロック全体を適用します。MDN によると、if()スタイルクエリでは単一のプロパティ値しか設定できませんが、@containerクエリはルールセット全体を条件付きで適用できます。 -
方向性。
if()はスタイリング対象の要素自身に宣言されたプロパティを読み取れます。@containerは自身の要素の値を読み取ってその要素をスタイリングすることはできず、祖先の値を読み取って子孫をスタイリングします。 -
スコーピング。
container-nameによるスコープ指定はコンテナスタイルクエリのみが可能です。コンテナに名前を付けると、クエリはそのコンテナのみに対して評価されます。指定した名前のコンテナがクエリ対象のプロパティを定義していない場合、ブロックは単純に実行されません。if()には同等のスコープ制御がなく、常に自身が置かれた要素に対して解決されます。
したがって、重複ゾーンでの判断基準は次のとおりです。1 つの要素の1 つのプロパティを切り替えるだけなら、if() の方が間接参照が少なくなります。複数の子孫が1 つの親の値に反応する必要がある場合は、@container が 1 つの条件でそれを表現できます(各子要素に if() を繰り返す必要がありません)。
よくある構文の落とし穴
if() 内のペアはコンマではなくセミコロンで区切ります。 JavaScript や rgb() のようなコンマ区切りの CSS 関数から来た人は必ずここでつまずきます。文法は if(condition-1: value-1; condition-2: value-2; else: fallback) で、最後のペアの後のセミコロンはオプションです。また、if と開き括弧の間にスペースがあると宣言全体が無効になります。条件が一致せず else を省略した場合、if() は「確実に無効な値」を返します。通常のプロパティコンテキストではプロパティの初期値に解決されますが、これはほぼ意図した結果ではありません。常に else を含めてください。
style() 内のコロン対イコールの違いは、ほとんど誰も説明しない微妙かつ重要な詳細です。 if() とコンテナスタイルクエリの両方が、genuinely 異なる動作を持つ 2 つの記法を受け付けます。これは現在 MDN に文書化されています。
プレーン(コロン)記法は計算値のトークン/文字列マッチングを行い、数値計算は行いません。レンジ(演算子)記法(=、<、<=、>、>= を使用)は各辺を解決し、両方を数値型として解析し、calc() を評価してから数値比較を行います。MDN 自身の例から直接引用した結果を示します。
.box { --n: calc(6 / 2); }
/* FALSE: --n の計算値は文字列 "calc(6/2)" であり、
トークン "3" とは一致しない */
@container style(--n: 3) { /* … */ }
/* TRUE: 両辺が <number> として解析され、calc(6/2) が 3 に計算され、
3 = 3 となる */
@container style(--n = 3) { /* … */ }
つまり、style(--n: 3) は --n: calc(6/2) に対して失敗しますが、style(--n = 3) は成功します。さらに 2 つのルールがあります。プレーン記法では、カスタムプロパティ名は var() なしで左辺に置く必要があります(style(var(--n): 3) は無効です)。一方、レンジ記法では名前、var()、リテラル、calc() を任意の順序でどちらの辺にも置けます。また、等価な値(blue と #0000ff など)がコロン記法でマッチするのは、プロパティが @property と syntax ディスクリプターで登録されている場合のみです。要点:キーワード/文字列マッチングにはコロン記法(style(--theme: dark))を使用し、数値比較には演算子記法(style(--columns >= 3))を使用してください。
レンジ構文は両方に搭載
レンジ構文は両方の仕組みを完全一致を超えた数値比較に拡張し、カスタムプロパティ、attr()、またはリテラル値からオペランドを取得します。Chrome 142 のリリースノートによると、比較は両辺が同じデータ型に解決される場合のみ有効で、<length>、<number>、<percentage>、<angle>、<time>、<frequency>、<resolution> の 7 つの数値型に限定されます。3 値の区間形式も機能します(@container style(0 < --n < 10))。両方の比較演算子は同じ方向を向く必要があります。
レンジ構文により、通知バッジのようなパターンがすっきりと書けます。属性からカウントを読み取り、しきい値で分岐します。
@container style(--count > 99) {
.badge::after { content: "99+"; }
}
レンジ記法は数値として解析するため、style(--s = new) は new のようなキーワードに対して false になりますが、style(--s: new) は true になります。これも記法の選択が重要である理由の一つです。
シナリオ別の使い分け
| シナリオ | 使用するもの | 理由 |
|---|---|---|
テーマトークン → 1 つのプロパティ(例:--theme から color) | if() | 1 つの要素がインラインで自身の値を決定する |
| コンポーネントの状態が複数の子スタイルを切り替える(active/error) | @container style() | 1 つの親の値が子孫全体のルールブロックを制御する |
| 単一プロパティのレスポンシブ調整 | if() と media() | ローカライズされたプロパティ単位のロジック;@media ブロック不要 |
| 通知カウントバッジのしきい値 | レンジ構文でどちらでも | 兄弟要素も反応する場合は @container;バッジのみ変化する場合は if() |
| テーマ付き親のコントラスト対応テキスト | @container style() | 子要素が親のテーマを読み取って一緒にスタイルを変更する |
機能ゲート付き値(例:lch() とフォールバック) | if() と supports() | サポート状況に基づく単一の値の切り替え |
| ブレークポイントでの複数プロパティのレイアウト変更 | @media(どちらでもない) | 大規模なレイアウト変更には両方とも不適切 |
経験則:条件付きの値が 1 つなら if() の仕事、サブツリーをスタイル変更する条件付きコンテキストなら @container の仕事です。
2026 年における実装
ブラウザサポートは三分されており、「条件付き CSS は Chromium のみ」という従来の認識はもはや正確ではありません。2026 年 6 月時点のブラウザサポート:
| 機能 | Chrome / Edge | Safari | Firefox |
|---|---|---|---|
| コンテナスタイルクエリ(カスタムプロパティ、プレーン構文) | 111+ | 18+ | 151+ |
if() 関数 | 137+ | 未実装 | 未実装 |
style() / if() のレンジ構文 | 142+(デフォルト有効) | 未実装 | 151、layout.css.attr.enabled フラグが必要 |
基本的なコンテナスタイルクエリは現在、真にクロスエンジンで利用可能です。Chrome と Edge 111 でリリースされ、Safari 18.0 で WebKit に実装され、2026 年 5 月 19 日にリリースされた Firefox 151 でコンテナのカスタムプロパティに基づく @container style() クエリサポートが追加されました。一方、if() 関数は依然として Chromium のみです。MDN は限定的な可用性を持つ実験的機能としてフラグを立てており、レンジ構文は Chromium でのみデフォルト有効です。Firefox 151 は @container style() レンジ構文を実装していますが、デフォルトでは無効で layout.css.attr.enabled 設定が必要です。
実際的な結論:if() は適切にデグレードしないため、最初にデフォルトの宣言を書いてサポートするブラウザでオーバーライドさせるか、@supports ガードでルールを囲んでください。以下のパターンで両方の仕組みをカバーできます。
/* フォールバックを先に — すべてのブラウザが適用する */
.card-title { font-weight: 400; }
/* プログレッシブエンハンスメント: スタイルクエリ対応エンジンのみ適用 */
@supports (container-type: normal) {
@container style(--status: active) {
.card-title { font-weight: 700; }
}
}
/* if(): 先に静的な値、次に条件付きオーバーライド */
.badge { padding: 0.25em; }
.badge { padding: if(style(--size: lg): 0.5em; else: 0.25em); }
if() とデフォルト有効のレンジ構文は、Chrome ユーザーの開発者がほとんど開かない Firefox や Safari でフォールバックパスにレンダリングされます。そのため、クロスブラウザのセッションリプレイは、フォールバックブランチが正しく表示されていると仮定するのではなく、実際のユーザーのブラウザでレンダリングを確認するのに有効な手法です。if() 内の calc() やネストパターンについては、LogRocket の判断に焦点を当てたウォークスルーがここでは扱いきれない詳細まで解説しています。
新しさではなく意図で選択してください。1 つの要素への条件付き値は if()、親からサブツリーをスタイル変更する条件付きコンテキストはコンテナスタイルクエリです。今日はフォールバックの後ろに実装し、本番環境で if() を使用する前にサポートマトリックスを再確認してください。この 2 つは、かつてカスタムプロパティのハックと JavaScript が必要だった条件付き CSS の領域をますますカバーするようになっています。
よくある質問
コンテナスタイルクエリは、if() のように同じ要素自身のカスタムプロパティを読み取れますか?
いいえ。コンテナスタイルクエリは常にカスケードを上方向に参照して祖先コンテナを探し、そのルールを子孫に適用します。そのため、スタイリングしようとしている要素自身のカスタムプロパティを読み取ることはできません。if() 関数はその逆です。同じ要素の同じルール内で宣言されたカスタムプロパティを読み取り、インラインで値を解決します。要素が自身のプロパティに基づいて分岐する必要がある場合は if() を使用し、親の値が子のスタイルを変更すべき場合はコンテナスタイルクエリを使用してください。
コンテナスタイルクエリはサイズクエリのように container-type の設定が必要ですか?
いいえ。すべての要素はデフォルトでスタイルコンテナであるため、カスタムプロパティのスタイルクエリは container-type を宣言しなくても機能します。これはコンテナサイズクエリとは異なります。サイズクエリでは、サイズ条件を解決する前に、クエリ対象の祖先に container-type(inline-size など)を設定する必要があります。これは MDN のコンテナクエリガイドに記載されており、@container アットルールを初めて使用する開発者が想定する主なセットアップ手順がなくなります。
calc() を使ったスタイルクエリが一致しないのはなぜですか?
プレーンのコロン記法はトークンと文字列のマッチングを行い、数値計算を行わないためです。calc(6 / 2) に設定されたプロパティは文字列 'calc(6/2)' に計算され、トークン 3 とは一致しないため、style(--n: 3) は false を返します。数値比較を行うには、比較演算子記法 style(--n = 3) を使用してください。これは各辺を解決し、calc() を評価し、両方を数値型として解析して数値比較を行うため、true を返します。キーワードマッチングにはコロン形式を、数値比較には演算子形式を使用してください。
if() は Safari と Firefox で適切にデグレードしますか?
いいえ。2026 年半ば時点で if() は Chromium ブラウザ(Chrome と Edge 137+)のみで動作するため、Safari と Firefox は if() を含む宣言を無視し、それ以前に適用されていた値にフォールバックします。静的なデフォルト宣言を先に書いてサポートするブラウザでオーバーライドさせるか、条件付きルールを @supports ガードで囲んでください。基本的なコンテナスタイルクエリはクロスエンジンで安全に使用でき、Chrome 111+、Edge 111+、Safari 18+、Firefox 151+ で動作しますが、デフォルト有効のレンジ構文は依然として Chromium のみです。
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Star on GitHub12k