はじめに
レスポンシブデザインといえば、長らく「メディアクエリで画面幅ごとにスタイルを切り替える」のが常識でした。
@media (min-width: 768px) {
.card {
display: flex;
}
}
しかしこの方法には、コンポーネント時代の開発と根本的に相性が悪い問題があります。それを解決するのが コンテナクエリ(Container Queries) です。2026年現在、全主要ブラウザ(Chrome / Firefox / Edge / Safari)で利用可能になり、実務投入のハードルは完全になくなりました。
この記事では、メディアクエリの何が問題だったのか、コンテナクエリでどう変わるのかを、実装例を交えて解説します。
メディアクエリの限界:「画面幅」しか見えない
メディアクエリが見ているのは ビューポート(画面全体)の幅 です。しかし現代のUIはコンポーネント単位で作られ、同じコンポーネントがさまざまな場所で使い回されます。
例えば、こんな Card コンポーネントを考えます。
- メインカラム(幅800px)では 横並びレイアウト で表示したい
- サイドバー(幅300px)では 縦積みレイアウト で表示したい
メディアクエリではこれが素直に書けません。画面幅が1200pxのとき、メインカラムのカードもサイドバーのカードも「同じ画面幅」として扱われるからです。結果として:
/* こういう配置依存のクラスが増殖していく… */
.card--in-sidebar { ... }
.card--in-main { ... }
.card--in-modal { ... }
「どこに置かれるか」をコンポーネント自身が知らなければならず、再利用性が崩壊します。JSで親要素の幅を監視する(ResizeObserver)という力技もありますが、本来スタイルの問題にJSを持ち出すのは避けたいところです。
コンテナクエリ:「自分が置かれた場所」に反応する
コンテナクエリは、ビューポートではなく 親コンテナの幅 を基準にスタイルを切り替えます。
基本の書き方
ステップは2つだけです。
① 親要素を「コンテナ」として宣言する
.card-wrapper {
container-type: inline-size;
}
② @container でコンテナ幅に応じたスタイルを書く
.card {
display: grid;
grid-template-rows: 200px auto; /* デフォルトは縦積み */
}
@container (min-width: 400px) {
.card {
grid-template-rows: none;
grid-template-columns: 160px 1fr; /* 広ければ横並び */
}
}
これだけで、同じCardコンポーネントが、メインカラムでは横並び、サイドバーでは縦積み に自動で切り替わります。カード自身は「自分がどのページのどこに置かれているか」を一切知る必要がありません。
コンテナに名前をつける
コンテナが入れ子になる場合は、名前をつけて明示的に参照できます。
.sidebar {
container-name: sidebar;
container-type: inline-size;
/* ショートハンド: container: sidebar / inline-size; */
}
@container sidebar (min-width: 250px) {
.widget {
padding: 1.5rem;
}
}
コンテナクエリ専用の単位
コンテナ基準の相対単位も使えます。vw / vh のコンテナ版です。
| 単位 | 意味 |
|---|---|
cqw |
コンテナ幅の1% |
cqh |
コンテナ高さの1% |
cqi |
コンテナのインラインサイズの1% |
cqmin / cqmax
|
cqi / cqb の小さい方・大きい方 |
.card-title {
/* コンテナ幅に応じてフォントサイズが滑らかに変わる */
font-size: clamp(1rem, 4cqi, 1.5rem);
}
メディアクエリのブレークポイントを待たずに、コンテナ幅へ流動的に追従するタイポグラフィが実現できます。
実践例:1つのCardを3つの文脈で使い回す
<main class="main-column">
<div class="card-wrapper"><article class="card">…</article></div>
</main>
<aside class="sidebar">
<div class="card-wrapper"><article class="card">…</article></div>
</aside>
/* すべてのラッパーをコンテナ化 */
.card-wrapper {
container-type: inline-size;
}
/* --- Cardコンポーネント(配置場所を知らない) --- */
.card {
display: grid;
gap: 0.75rem;
}
.card img {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
/* 400px以上あれば横並び */
@container (min-width: 400px) {
.card {
grid-template-columns: 160px 1fr;
align-items: center;
}
}
/* 600px以上あればさらにリッチに */
@container (min-width: 600px) {
.card {
grid-template-columns: 240px 1fr;
gap: 1.5rem;
}
.card .description {
display: block; /* 狭い場所では非表示だった説明文を表示 */
}
}
CSSはコンポーネントに1回書くだけ。あとはどこに置いても勝手に最適なレイアウトになります。
では、メディアクエリは不要になるのか?
なりません。役割分担が明確になった、というのが正しい理解です。
| 用途 | 使うべきもの |
|---|---|
| ページ全体のレイアウト(カラム数、サイドバーの有無) | メディアクエリ |
ダークモード、prefers-reduced-motion などユーザー設定 |
メディアクエリ |
| コンポーネント内部のレイアウト切り替え | コンテナクエリ |
| コンポーネント内の流動的なサイズ調整 | コンテナクエリ単位(cqi など) |
「ページはメディアクエリ、コンポーネントはコンテナクエリ」 と覚えておけばOKです。ページレイアウトがビューポートに反応してカラム幅を変え、その中のコンポーネントはコンテナクエリで勝手に追従する——これが2026年のレスポンシブ設計の基本形です。
移行のポイントと注意点
1. container-type を指定した要素はサイズ封じ込めが効く
container-type: inline-size を指定すると、その要素にサイズの封じ込め(containment)が適用されます。具体的には、子要素の大きさに基づいて自身の幅を決められなくなる ため、float や width: max-content 的な挙動に依存しているレイアウトでは崩れる可能性があります。コンテナ化するのは「レイアウト上、幅が親から決まるラッパー要素」にするのが安全です。
2. コンテナ自身にはクエリが効かない
@container でスタイルを変えられるのは コンテナの子孫要素 です。コンテナ自身のスタイルを変えたい場合は、さらに外側にラッパーを用意します。
3. 段階的移行がしやすい
コンテナクエリは既存のメディアクエリと共存できます。新規コンポーネントから導入し、既存部分は触らない、という進め方が現実的です。全主要ブラウザ対応済みですが、古い環境も考慮するなら @supports が使えます。
@supports (container-type: inline-size) {
/* コンテナクエリ前提のスタイル */
}
まとめ
- メディアクエリは「画面幅」しか見えず、コンポーネントの再利用と相性が悪かった
- コンテナクエリは「親コンテナの幅」に反応するので、コンポーネントが配置場所を知らなくてよくなる
-
cqiなどのコンテナ単位で流動的なサイズ調整も可能 - メディアクエリが不要になるわけではなく、ページ=メディアクエリ / コンポーネント=コンテナクエリ の役割分担へ
- 2026年現在、全主要ブラウザ対応済みで実務投入に障害なし
コンポーネント駆動の開発(React / Vue / Web Components など)をしているなら、恩恵は特に大きいはずです。まずは使い回しの多いCardやリストアイテムから置き換えてみてください。