コンテナクエリは、画面幅ではなく「親要素の大きさ」を条件にスタイルを切り替えるCSSの仕組みです。同じカード部品を、幅の広いメイン欄では横並び、狭いサイドバーでは縦積みにする、といった出し分けをCSSだけで書けます。この記事では、container-type の宣言から @container の条件式と組み合わせ方、cqi などの単位、2026年10月時点の対応ブラウザ、DevToolsでの確認、そして「書いたのに効かない」ときの原因までを、仕様書とMDNの互換性データ、Chrome 154・156での実測をもとに順に説明します。
まとめ:コンテナクエリの要点
- 基準になる親要素に
container-type: inline-sizeを指定し、子孫のスタイルを@container (width > 30rem) { … }で切り替えます。 - メディアクエリはビューポート(画面)を、コンテナクエリは最も近い祖先コンテナを見ます。ページ全体の骨組みはメディアクエリ、使い回す部品の内部はコンテナクエリ、という分担が基本です。
- サイズのコンテナクエリと
cqiなどの単位は Chrome・Edge 105、Firefox 110、Safari 16 から使え、2025年8月に Baseline Widely available になりました。 - 条件は
and・or・notで組み合わせられます。別々のコンテナを見る条件をカンマで並べる書き方は Chrome 150 で入った新しい機能です。 - スタイルクエリ(
style())はカスタムプロパティ限定で、Firefox 151 の対応により2026年5月に主要ブラウザがそろいました。scroll-state()は Chromium 系だけです。 - サイズ封じ込めによって、中身から幅が決まらなくなる場合があります。横書きの flex の子や inline-block では、外側から幅を確保していないと内容領域の幅が0になることがあります。
以下、書き方の3ステップ、条件の組み合わせ、実装例、単位、対応状況、つまずきやすい点、JavaScriptからの判定の順に見ていきます。
コンテナクエリの判定基準とメディアクエリとの違い
メディアクエリの @media (width > 768px) は、画面の幅が768pxを超えたかどうかで判定します。部品がどこに置かれているかは見ません。そのため、同じカードを3列グリッドの1マスとサイドバーに置くと、画面幅は同じでもカードの実際の幅は大きく違い、メディアクエリだけでは出し分けられません。
コンテナクエリは、container-type を指定した祖先要素(クエリコンテナ)の大きさで判定します。部品は自分の置き場所の幅に反応するので、置き場所ごとに別クラスを用意する必要がなくなります。
| 比較軸 | メディアクエリ | コンテナクエリ |
|---|---|---|
| 判定の基準 | ビューポート・端末の特性 | 最も近い祖先コンテナ |
| 事前の宣言 | 不要 | サイズ・状態は container-type、style() は不要 |
| 判定できるもの | 幅・高さ・向き・prefers-* 等 | 幅・高さ・カスタムプロパティ・スクロール状態 |
| 向いている対象 | ページ全体のレイアウト | 使い回す部品の内部 |
メディアクエリが不要になるわけではありません。ダークモードの prefers-color-scheme や、印刷用の @media print は端末や利用者の設定に関わる条件なので、コンテナクエリでは代わりになりません。ページの列数やブレークポイントの決め方はメディアクエリとブレークポイント設計の実装手順で扱っています。
コンテナクエリの書き方3ステップ
container-typeによる基準コンテナの宣言
最初に、どの要素を基準にするかを宣言します。container-type を指定しない要素は、サイズの問い合わせ先になりません。
| 値 | 問い合わせできる対象 | 主な用途 |
|---|---|---|
| inline-size | インライン方向(横書きなら幅) | 通常はこれを使う |
| size | 幅と高さの両方 | 高さが明示された枠 |
| normal(初期値) | サイズは不可・スタイルのみ | style() だけ使う場合 |
| scroll-state | スクロール状態 | 固定ヘッダー等(Chromium のみ) |
迷ったら inline-size を選びます。size は高さ方向も中身から決まらなくなるため、height や親のレイアウトなどで高さを確保しないと、内容領域の高さが0になることがあります(後述)。仕様上、inline-size と size は要素にスタイル封じ込め(style containment)とサイズ封じ込めを適用し、独立した整形コンテキストを作ります。
@containerの条件式と範囲構文
条件は @container のあとに括弧で書きます。> や <= などの比較演算子を使う範囲構文が使えます。
.card-wrap {
container-type: inline-size;
}
/* 親の幅が 30rem を超えたら横並び */
@container (width > 30rem) {
.card { display: flex; gap: 1rem; }
}
/* 範囲構文:400px 以上 700px 以下 */
@container (400px <= width <= 700px) {
.card__title { font-size: 1.25rem; }
}
条件に書けるサイズの特徴は width・height・inline-size・block-size・aspect-ratio・orientation です。古い記事にある min-width 形式も有効ですが、範囲構文のほうが境界の含み方(< か <= か)を読み違えにくくなります。Chrome 154 で幅500pxと800pxのコンテナに上の範囲構文を当てると、500px側だけが一致しました。
container-nameによる問い合わせ先の指定
名前を付けない @container は、条件の軸を持つ最も近い祖先コンテナを見ます。入れ子の内側にもコンテナがあると、外側のレイアウト枠ではなく内側が判定に使われます。外側を見たいときは名前で指定します。
.layout-main {
container: main-area / inline-size; /* name / type の短縮形 */
}
@container main-area (width > 60rem) {
.card-list { display: grid; grid-template-columns: repeat(3, 1fr); }
}
/* 条件なし:main-area の中にいるときだけ適用 */
@container main-area {
.card { border: 1px solid #ccc; }
}
最後の「名前だけのクエリ」は、条件部分を省略できるよう仕様が改訂されたもので、Chrome・Edge 148、Firefox 149、Safari 26.4 が対応済みです(web-features では 2026-05-07 に Baseline Newly available)。対応していないブラウザでは規則全体が無視されるため、レイアウトの必須部分には使わないほうが安全です。
and・or・notとカンマ区切りによる条件の組み合わせ
1つのコンテナに対する条件は、メディアクエリと同じく and・or・not でつなぎます。仕様の文法では、and と or を括弧なしで混ぜることはできません。混ぜたいときは括弧で囲んでまとまりを作ります。
/* 幅が 20rem 以上 40rem 未満 */
@container (width >= 20rem) and (width < 40rem) { … }
/* どちらか一方が真なら適用 */
@container (width > 60rem) or (width < 20rem) { … }
/* 否定 */
@container not (width > 30rem) { … }
/* 別々のコンテナをカンマで並べる(どれか1つが一致すれば適用) */
@container sidebar (width < 20rem), main-area (width > 60rem) { … }
Chrome 156 で幅300pxのコンテナに当てると、(width > 500px) or (width < 400px) と not (width > 500px) は一致しました。一方、(width > 500px) or (orientation: portrait) は一致しませんでした。orientation と aspect-ratio は高さも使うので、container-type: size のコンテナでなければ判定できません。inline-size のコンテナは高さの問い合わせに対応しないため、この複合条件の問い合わせ先から外れます。さらに外側に条件全体を評価できる size コンテナがあれば、そのコンテナで判定します。適格な祖先がなければ結果は不明(unknown)となり、規則は適用されません。
最後のカンマ区切りは、名前の違う複数のコンテナを1つの規則で問い合わせる書き方です。Chromium は「Intent to Ship」(blink-dev、2026年5月11日)で Chrome 150 からの出荷を告知しました。Chrome 156 では、幅300pxの alpha と幅600pxの beta に alpha (width > 500px), beta (width > 500px) を当てると、beta の中だけに適用されました。MDN の互換性データ(8.1.7)にはまだこの項目が無く、Firefox と Safari の対応版は確認できません。対象ブラウザがそろうまでは、規則を2つに分けて書くほうが安全です。
親の幅でカード部品を切り替える実装例
サイドバーでは縦積み、メイン欄では画像と本文を横並びにするカードの例です。コンテナにするのはカード自身ではなく、カードを包む要素です(理由は「サイズクエリの参照先と自己参照の制限」の節で説明します)。
<div class="card-wrap">
<article class="card">
<img class="card__img" src="thumb.png" alt="">
<div class="card__body">
<h3 class="card__title">記事タイトル</h3>
<p>概要文</p>
</div>
</article>
</div>
.card-wrap { container-type: inline-size; }
.card { display: grid; gap: .75rem; }
.card__img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
@container (width >= 32rem) {
.card { grid-template-columns: 12rem 1fr; }
.card__img { aspect-ratio: 1; }
}
.card__title { font-size: clamp(1rem, 4cqi, 1.5rem); }
このCSSはサイドバーにもメイン欄にも同じまま置けます。画面幅で分けていた頃は .sidebar .card のような置き場所ごとの上書きが増えがちでしたが、それが不要になります。カードを並べる側を repeat(auto-fit, minmax(16rem, 1fr)) にすると、列数の変化に合わせてカードの見た目も自動で切り替わります(グリッド側はCSS Gridのauto-fitとsubgridの実装を参照)。
スマートフォンとPCで同じ部品を出し分けるサイトの制作は、スマートフォンサイト制作の支援でも扱っています。
Tailwind CSS v4 は本体に @container と @md:flex のような変種が入っており、v3 ではプラグインが別途必要でした。版ごとの違いはTailwind CSS v3とv4の違いにまとめています。
コンテナクエリ単位cqi・cqwの使い方
コンテナの大きさに対する割合で長さを指定する単位です。1単位はコンテナの該当寸法の1%です。
| 単位 | 基準 |
|---|---|
| cqw / cqh | コンテナの幅 / 高さの1% |
| cqi / cqb | インライン方向 / ブロック方向のサイズの1% |
| cqmin / cqmax | cqi と cqb の小さい方 / 大きい方 |
物理的な幅を基準にするなら cqw、文字が進むインライン方向を基準にするなら cqi を使います。縦書きでは cqi は高さに対応します。横書きのコンテナの高さを参照するには container-type: size が必要ですが、縦書きのコンテナの高さなら inline-size でも cqh の基準になります。
該当するコンテナが祖先に無い場合、仕様はビューポートのスモールサイズ(svw などと同じ基準)を使うと定めています。Chrome 154 で幅800pxの画面にコンテナを置かずに width: 50cqi を指定すると、400px になりました。エラーにならず画面基準で黙って動くため、「コンテナ指定を忘れているのに一見それらしく表示される」状態に気づきにくい点に注意してください。文字サイズに使うときは、上の例のように clamp() で上限と下限を付けると、極端に狭い・広いコンテナで読めなくなるのを防げます。
対応ブラウザとBaselineの状況(2026年10月時点)
MDN の互換性データ(browser-compat-data 8.1.7、2026-10-22)と web-features をもとに、機能ごとの初対応バージョンを整理しました。仕様本文は W3C の CSS Conditional Rules Module Level 5 です。
| 機能 | Chrome・Edge | Firefox | Safari | Baseline |
|---|---|---|---|---|
| サイズクエリ・cq単位 | 105 | 110 | 16 | Widely(2025-08-14) |
| スタイルクエリ | 111 | 151 | 18 | Newly(2026-05-19) |
| 名前だけのクエリ | 148 | 149 | 26.4 | Newly(2026-05-07) |
| スタイルクエリの範囲構文 | 142 | 151 | 未対応 | なし |
| scroll-state() | 133 | 未対応 | 未対応 | なし |
| anchored | 143 | 未対応 | 未対応 | なし |
サイズのコンテナクエリは、3ブラウザがそろった2023年2月から30か月が経ち、2025-08-14 に Widely available になりました。採用を検討しやすい段階ですが、フォールバックの要否は対象ブラウザと訪問者の利用環境を確認して決めます。Baseline の2段階の意味と確認方法はWeb Platform Baselineとはで解説しています。
業務システムなどで古い端末(iOS 15 以前の Safari など)を支える必要がある場合は、コンテナクエリの無い状態でも読めるレイアウト(1列の縦積み)を既定にし、横並びをコンテナクエリの中に書きます。未対応ブラウザは @container の中身を丸ごと無視するので、それだけで段階的な強化になります。判定を明示したいときは @supports (container-type: inline-size) で囲みます。
スタイルクエリ・scroll-state・anchoredの現状
style()はカスタムプロパティだけが対象
スタイルクエリは、祖先コンテナの計算値を条件にします。サイズと違い container-type の指定は不要で、仕様上はすべての要素がスタイルクエリの問い合わせ先になります。ただし、MDNの互換性データではSafariに文書ルート要素を問い合わせ先にできない制限が記載されています。
.panel { --theme: dark; background-color: #222; }
@container style(--theme: dark) {
.panel__title { color: #fff; }
}
仕様は通常のプロパティも条件にできる書き方ですが、各ブラウザが実装しているのはカスタムプロパティだけです。Chrome 154 で親に color: red を当てて style(color: red) を書いても一致しませんでした。--theme のようなカスタムプロパティを旗として立てる使い方に限られます。style(--n > 3) のような範囲比較は Safari が未対応なので、2026年10月時点では Chromium と Firefox 向けの機能です。
scroll-state()とanchoredはChromium限定
container-type: scroll-state を付けると、position: sticky の要素が貼り付いたか(stuck)、スクロールスナップで吸着したか(snapped)、スクロールできる余地があるか(scrollable)で子孫のスタイルを変えられます。Chrome 133 で入り、直前にスクロールした方向を見る scrolled は Chrome 144 からです。
.site-header {
position: sticky;
top: 0;
container-type: scroll-state;
}
/* ヘッダーが上端に貼り付いている間だけ影を付ける */
@container scroll-state(stuck: top) {
.site-header__inner { box-shadow: 0 2px 4px rgb(0 0 0 / .2); }
}
条件で変えられるのはコンテナの子孫だけなので、影はヘッダー自身ではなく内側の要素に付けます。container-type: anchored(Chrome 143)は、アンカー配置の要素が代替位置に切り替わったかを判定するもので、CSS Anchor Positioningと組み合わせて使います。どちらも Firefox と Safari は未対応なので、見た目の装飾にとどめ、機能に関わる表示の切り替えには使わないでください。
コンテナクエリが効かない・幅が0になる原因
書き方は合っているのに期待どおり動かないときは、まずDevToolsでどのコンテナが判定に使われているかを確かめます。そのうえで、サイズ封じ込めによる寸法の潰れ、参照先の制限、旧ブラウザでの配置の違いを疑います。本章の実測値は、疑似要素の例を除き Chrome 154.0.8037.58(macOS、ビューポート800×600、2026年10月1日)で確認したものです。疑似要素の例は Chrome 156.0.8078.48(同環境、2026年10月29日)で確認しています。
DevToolsのcontainerバッジによる判定先の確認
Chrome の DevTools では、container-type を指定した要素の横に、Elements パネルで container バッジが表示されます。バッジを押すと、そのコンテナと子孫が点線で囲まれます。バッジが出ない要素は、サイズの問い合わせ先になっていません。
子孫の要素を選ぶと、Styles ペインに適用中の @container 規則が、どのコンテナを見ているかのリンク付きで表示されます。条件の数値はほかの宣言と同じようにその場で書き換えられます(Chrome DevTools のドキュメント)。想定と違うコンテナが表示されたら、入れ子の内側にあるコンテナが拾われていないかを疑い、container-name で外側を指定します。画面幅を変えずにコンテナだけを伸縮させて試すなら、検証中だけコンテナに resize: horizontal; overflow: auto; を付けると、右下のつまみで幅を変えられます。
flexの子・inline-blockで幅が0になる条件
container-type: inline-size を付けた要素は、インライン方向(横書きなら横方向)の大きさを「中身が無いものとして」計算します(CSS Containment Level 2 の inline-size containment の定義)。幅を中身から決める配置、つまり display: flex の子(幅指定なし)、inline-block、float、width: fit-content などでは、横書きで外側から幅が確保されず、最小幅や代替の内在サイズも設定されていないと、内容領域の幅が0になることがあります。実測でも flex の子と inline-block はどちらも幅0でした。一方、grid-template-columns: 1fr 1fr のマスに置いた要素は400pxと正常でした。
対処は、コンテナにする要素の幅を外側から決めることです。flex の子なら flex: 1 や width を与えるか、幅が自然に決まるブロック要素のラッパーを1段足してそちらをコンテナにします。
サイズクエリの参照先と自己参照の制限
@container が見るのは祖先だけで、container-type を付けた要素自身の幅は、その要素のスタイル条件になりません。実測では、幅500pxのコンテナの中にある幅300pxのコンテナ自身に @container (width > 400px) を書くと、条件は一致しました。自分の300pxではなく、外側の500pxで判定されたためです。祖先にコンテナが無い幅300pxの要素に同じ形の条件を書いても、何も適用されませんでした。逆に、300pxのコンテナの子に書いた @container (width > 400px) は、外側の500pxではなく最も近い300pxの方で判定され、一致しませんでした。カード自身ではなくラッパーをコンテナにする、という前章の書き方はこのためです。
例外は疑似要素です。仕様は、::before・::after・::marker・::backdrop が元の要素(originating element)を問い合わせると定めています。そのため、コンテナ自身の ::after にはそのコンテナの幅で判定した条件が効きます。Chrome 156 でも、幅300pxのコンテナに @container (width > 200px) { .box::after { … } } を書くと一致しました。
container-type: sizeで高さが0になる条件
size は縦方向も中身から決まらなくなるので、height、aspect-ratio、最小高さ、親のレイアウトなどで高さを確保していない場合、内容領域の高さが0になることがあります。実測でも、中に段落があるのに高さは0でした。高さで判定する必要がなければ inline-size を使います。
position: fixedの配置基準とブラウザの版による差
初期の仕様では、コンテナはレイアウト封じ込め(layout containment)も適用していました。この場合、子孫の position: fixed はビューポートではなくコンテナを基準に配置されます。仕様はこれを改め、現在は幅・高さのコンテナにレイアウト封じ込めを適用しません。新しい挙動になったのは Chrome・Edge 129、Firefox 133、Safari 18.4 です。Chrome 154 では、コンテナ内の position: fixed; top: 0; left: 0 の要素は画面の左上(0, 0)に置かれました。コンテナの中にモーダルや追従ボタンを置く場合、これより古いブラウザでは位置がずれる可能性があります。
JavaScriptからコンテナの幅を判定する方法
メディアクエリには window.matchMedia() がありますが、コンテナクエリにはまだ対応するメソッドがありません。CSS Working Group は要素ごとに判定する matchContainer() と ContainerQueryList を、2026年10月27日に仕様の編集者草案へ追加しました(PR #13551。発端は CSSWG issue #6205)。ただし草案には名前を変える可能性が注記されており、Chrome 156 では Element.prototype.matchContainer は undefined でした。
幅の条件は ResizeObserver で内容領域の寸法を監視して再現できます。コンテナクエリの判定も内容領域(content box)の大きさを使うので、contentBoxSize を読めば CSS 側と基準がそろいます。以下は横書きを前提とする例です。縦書きでは inlineSize は高さを表すため、そのまま幅の判定には使えません。スタイルやスクロール状態の変化は寸法が変わらないので、このコードでは検出できません。
const wrap = document.querySelector('.card-wrap');
const card = wrap.querySelector('.card');
const ro = new ResizeObserver(([entry]) => {
const inline = entry.contentBoxSize[0].inlineSize;
// CSS の (width >= 32rem) と同じ境界(ルートの文字サイズ 16px の場合 512px)
card.dataset.wide = inline >= 512 ? 'true' : 'false';
});
ro.observe(wrap);
Chrome 156 では、幅600pxのラッパーで true、400pxに縮めると false に変わりました。rem を使う条件をJavaScriptで再現するときは、ルートの文字サイズが利用者の設定で変わる点に注意し、getComputedStyle(document.documentElement).fontSize から換算してください。
コンテナクエリを使わないほうがよい場面
次の場面ではメディアクエリか、別の手段のほうが適しています。
- ページ全体の列構成:ヘッダーやサイドバーの出し入れはビューポートで決まる話です。
body直下をコンテナにしても、その要素の内容領域の幅を参照するため、余白や最大幅の指定によってはビューポート幅と異なります。画面幅で分岐したい場合はメディアクエリで直接指定します。 - 利用者の設定や端末の特性:
prefers-reduced-motion・prefers-color-scheme・hover・pointerはメディアクエリでしか判定できません。 - レスポンシブ画像の候補選び:
<img>のsizesや<source media>の条件式には、コンテナクエリを直接指定できません(CSSWG issue #5889 は未解決)。通常はメディア条件と想定表示幅を指定して画像候補を選び、表示寸法をコンテナクエリで変えます。ただし、対応ブラウザではloading="lazy"の画像にsizes="auto"を指定すると、描画幅を候補選択に使えます(MDN の互換性データでは Chrome 126、Firefox 150、Safari 27 から)。その場合も画像の寸法を属性やCSSで確保します。 - Grid や Flexbox の折り返しだけで済むもの:
auto-fitやflex-wrapで並びが変わればよいだけなら、条件分岐を書く必要はありません。
コンテナクエリが最も効くのは、置き場所が複数あり、場所ごとに内部の配置を変えたい部品です。デザインシステムのカード、一覧のアイテム、ダッシュボードのウィジェットがこれに当たります。
コンテナクエリに関するよくある質問
コンテナクエリとは何ですか?
親要素(クエリコンテナ)の大きさや状態に応じて、子孫のスタイルを切り替えるCSSの機能です。サイズを条件にする場合は祖先に container-type を指定し、@container 規則に条件を書きます(スタイルクエリでは指定不要)。部品が置かれた場所の幅に反応するため、同じ部品をサイドバーとメイン欄で使い回せます。
コンテナクエリの単位は何がありますか?
cqw・cqh・cqi・cqb・cqmin・cqmax の6種類で、いずれもコンテナの該当寸法の1%を表します。文字が進むインライン方向を基準にするなら cqi、物理的な幅を基準にするなら cqw を使います。祖先にコンテナが無いときは画面のスモールビューポートが基準になり、エラーにはなりません。対応ブラウザはサイズのコンテナクエリと同じで、Chrome 105、Firefox 110、Safari 16 からです。
container-typeのsizeとinline-sizeはどちらを使えばよいですか?
通常は inline-size です。inline-size は横方向(横書きの場合)の幅だけを問い合わせ可能にし、高さは中身に応じて伸びます。size は高さでも判定できる代わりに、高さが中身から決まらなくなるため、height や親のレイアウトなどで高さを確保しないと、内容領域の高さが0になることがあります。高さ固定のパネルやスライドの枠など、高さが外から決まっている要素でだけ size を使います。
メディアクエリとコンテナクエリはどちらを使うべきですか?
役割が違うので併用します。ページ全体の列数や、ダークモード・印刷など端末と利用者の設定に関わる条件はメディアクエリ、置き場所によって使える幅が変わる部品の内部はコンテナクエリで書きます。部品側をメディアクエリに依存させなければ、レイアウトを組み替えても部品のCSSを直さずに済みます。
SafariやiPhoneでもコンテナクエリは使えますか?
サイズのコンテナクエリと cq 単位は Safari 16(iOS 16)から、スタイルクエリは Safari 18 から使えます。scroll-state()、anchored、スタイルクエリの範囲比較は2026年10月時点の Safari では未対応です。iOS 15 以前も対象なら、1列の表示を既定にして対応ブラウザでだけ横並びにします。