📦
メディアクエリの限界を超える!CSS Container Queries完全ガイド
はじめに:メディアクエリの限界
レスポンシブデザインといえば、長年メディアクエリが主役でした。
しかし、メディアクエリには致命的な欠点があります。
/* メディアクエリは viewport の幅でしか判断できない */
@media (min-width: 768px) {
.card {
display: flex; /* タブレット以上は横並び */
}
}
この何が問題なのでしょうか?
問題1:コンポーネントの配置場所に依存してしまう
例えば、以下のようなカードコンポーネントがあるとします:
<!-- メインエリア(広い)に配置 -->
<main class="main-area">
<div class="card">...</div>
</main>
<!-- サイドバー(狭い)に配置 -->
<aside class="sidebar">
<div class="card">...</div>
</aside>
メディアクエリの場合:
- viewport が768px以上なら、どちらのカードも横並びレイアウトになる
- サイドバーのカードは狭いのに、無理やり横並びになってレイアウトが崩れる
本当にやりたいこと:
- カードの親要素の幅に応じてレイアウトを変えたい
- メインエリアのカードは横並び
- サイドバーのカードは縦並び(親が狭いから)
これがメディアクエリの限界です。
問題2:コンポーネントの再利用性が低い
/* このカードはメインエリア専用になってしまう */
.main-area .card {
display: flex;
}
/* サイドバー用に別のスタイルが必要... */
.sidebar .card {
display: block;
}
配置場所ごとにスタイルを書く必要があり、コンポーネント指向の設計ができません。
CSS Container Queries が解決する
Container Queries を使えば、親要素の幅に応じてスタイルを変えられます。
/* 親要素をコンテナとして定義 */
.card-container {
container-type: inline-size;
}
/* 親の幅が400px以上なら横並び */
@container (min-width: 400px) {
.card {
display: flex;
}
}
これで:
- メインエリアのカードは横並び(親が広いから)
- サイドバーのカードは縦並び(親が狭いから)
- 同じCSSで、配置場所に関係なく適切なレイアウトになる
基本的な使い方
ステップ1:親要素をコンテナとして定義
.container {
container-type: inline-size; /* この要素をコンテナにする */
}
ステップ2:@containerルールを書く
@container (min-width: 400px) {
/* コンテナの幅が400px以上の時のスタイル */
.card {
display: flex;
}
}
完全な例
<div class="card-container">
<div class="card">
<img src="image.jpg" alt="">
<div class="content">
<h2>タイトル</h2>
<p>説明文...</p>
</div>
</div>
</div>
/* 親をコンテナとして定義 */
.card-container {
container-type: inline-size;
}
/* デフォルト:縦並び */
.card {
display: block;
}
.card img {
width: 100%;
height: 200px;
object-fit: cover;
}
/* コンテナの幅が400px以上:横並び */
@container (min-width: 400px) {
.card {
display: flex;
gap: 1rem;
}
.card img {
width: 200px;
height: auto;
}
}
container-type の種類と使い分け
container-type には3つの値があります:
1. inline-size(⭐ 最もよく使う)
.container {
container-type: inline-size;
}
- インライン方向(通常は幅)のみをコンテナサイズとして扱う
- 高さは無視される
- パフォーマンスが良い(高さの再計算が不要)
- 99%のケースでこれを使えばOK
2. size
.container {
container-type: size;
}
- 幅と高さの両方をコンテナサイズとして扱う
- 高さにも依存したレイアウトが必要な場合
- パフォーマンスに注意(レイアウトシフトのリスク)
3. normal(デフォルト)
.container {
container-type: normal;
}
- コンテナクエリを使わない(通常の要素)
- 明示的に書く必要はない
container-name でターゲット指定
複数のコンテナが入れ子になっている場合、どのコンテナに対してクエリを書くか指定できます。
/* 名前付きコンテナ */
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
.main {
container-type: inline-size;
container-name: main;
}
/* sidebarコンテナに対するクエリ */
@container sidebar (min-width: 300px) {
.widget {
padding: 1rem;
}
}
/* mainコンテナに対するクエリ */
@container main (min-width: 800px) {
.article {
columns: 2;
}
}
省略記法:
.sidebar {
/* container-type と container-name を同時に指定 */
container: sidebar / inline-size;
}
実際に動くデモを見てみよう
まずは実際に動くデモで、Container Queriesの威力を体感してください!
デモ1:カードコンポーネント(メイン+サイドバー)
👆 ブラウザの幅を変えて、メインエリアとサイドバーのカードの挙動を比較してください
- メインエリア(広い):親が広いので横並びレイアウト
- サイドバー(狭い):親が狭いので縦並びレイアウト
- 同じHTML、同じCSSなのに自動的にレイアウトが変わります
デモ2:Container Query Units(cqw)
👆 コンテナの幅に応じて、フォントサイズが自動的にスケールします
-
cqw(Container Query Width)を使用 - メディアクエリ不要で、流動的なタイポグラフィを実現
実用パターン1:カードコンポーネント
最も一般的な使い方です。
<!-- メインエリア(広い)-->
<main class="main-area">
<div class="card-container">
<div class="card">
<img src="image.jpg" alt="">
<div class="content">
<h2>記事タイトル</h2>
<p>説明文が入ります...</p>
</div>
</div>
</div>
</main>
<!-- サイドバー(狭い)-->
<aside class="sidebar">
<div class="card-container">
<div class="card">
<img src="image.jpg" alt="">
<div class="content">
<h2>記事タイトル</h2>
<p>説明文が入ります...</p>
</div>
</div>
</div>
</aside>
.card-container {
container-type: inline-size;
}
/* デフォルト:縦並び(狭い時) */
.card {
display: block;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.card img {
width: 100%;
height: 200px;
object-fit: cover;
}
.card .content {
padding: 1rem;
}
/* コンテナの幅が400px以上:横並び */
@container (min-width: 400px) {
.card {
display: flex;
gap: 1rem;
}
.card img {
width: 200px;
height: 150px;
flex-shrink: 0;
}
}
/* コンテナの幅が600px以上:さらに調整 */
@container (min-width: 600px) {
.card img {
width: 250px;
height: 180px;
}
.card .content {
padding: 1.5rem;
}
.card h2 {
font-size: 1.5rem;
}
}
ポイント:
- 同じHTML、同じCSSで、配置場所によって自動的にレイアウトが変わる
- メインエリアでは横並び、サイドバーでは縦並びになる
実用パターン2:グリッドの列数に応じた調整
<div class="grid-container">
<div class="product-card">...</div>
<div class="product-card">...</div>
<div class="product-card">...</div>
</div>
.grid-container {
container-type: inline-size;
display: grid;
gap: 1rem;
/* 自動的に列数が変わる */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.product-card {
container-type: inline-size;
}
/* カードの幅が150px未満:テキストを省略 */
@container (max-width: 149px) {
.product-card .title {
font-size: 0.875rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.product-card .description {
display: none; /* 説明文を非表示 */
}
}
/* カードの幅が150px以上:通常表示 */
@container (min-width: 150px) {
.product-card .title {
font-size: 1rem;
}
.product-card .description {
display: block;
}
}
Container Query Units(コンテナ単位)
Container Queries では、コンテナのサイズを基準とした単位が使えます。
| 単位 | 意味 |
|---|---|
cqw |
コンテナの幅の1% |
cqh |
コンテナの高さの1% |
cqi |
コンテナのインライン方向の1% |
cqb |
コンテナのブロック方向の1% |
cqmin |
cqi と cqb の小さい方 |
cqmax |
cqi と cqb の大きい方 |
viewport単位との違い:
/* viewport基準 */
.title {
font-size: 5vw; /* viewport の幅の5% */
}
/* コンテナ基準 */
.card-container {
container-type: inline-size;
}
.card .title {
font-size: 5cqw; /* コンテナの幅の5% */
}
実用例:
.card-container {
container-type: inline-size;
}
/* コンテナの幅に応じてフォントサイズを自動調整 */
.card .title {
font-size: clamp(1rem, 4cqw, 2rem);
}
.card .description {
font-size: clamp(0.875rem, 2.5cqw, 1rem);
}
これで、どんな幅のコンテナに配置しても、自動的に適切なフォントサイズになります。
メディアクエリとの使い分け
両方を組み合わせることで、最も柔軟なレスポンシブデザインが実現できます。
メディアクエリを使うべき場面
-
グローバルなレイアウト変更
- ヘッダーのナビゲーション表示/非表示
- サイドバーの表示/非表示
- 全体のグリッド構造の変更
/* グローバルレイアウト */
@media (min-width: 768px) {
.layout {
display: grid;
grid-template-columns: 250px 1fr;
}
}
Container Queriesを使うべき場面
-
コンポーネント単位の調整
- カードの内部レイアウト
- ボタンのサイズ調整
- テキストの表示/非表示
/* コンポーネント内部 */
.card-container {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
display: flex;
}
}
併用パターン
/* グローバル:タブレット以上でサイドバー表示 */
@media (min-width: 768px) {
.layout {
display: grid;
grid-template-columns: 250px 1fr;
}
}
/* コンポーネント:親の幅に応じてカードを調整 */
.card-container {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
display: flex;
}
}
パフォーマンスへの影響
container-type: inline-size は安全
- 高さの再計算が不要なので、パフォーマンスへの影響はほぼない
- レイアウトシフトのリスクも低い
container-type: size は注意が必要
- 高さも含めてコンテナサイズとして扱うため、レイアウトの再計算が発生しやすい
- 特に、コンテンツの高さが動的に変わる場合は注意
- 必要な場合のみ使う
ベストプラクティス
/* ✅ 推奨:inline-size を使う */
.container {
container-type: inline-size;
}
/* ⚠️ 注意:size は必要な場合のみ */
.special-container {
container-type: size;
height: 100vh; /* 高さを固定する */
}
ブラウザサポート状況
2023年2月以降、主要ブラウザでサポート開始
| ブラウザ | サポート開始 |
|---|---|
| Chrome | 105+(2022年8月) |
| Edge | 105+(2022年9月) |
| Safari | 16.0+(2022年9月) |
| Firefox | 110+(2023年2月) |
2025年現在:もう安心して使えます!
フォールバック戦略
古いブラウザのサポートが必要な場合:
/* フォールバック:メディアクエリ */
@media (min-width: 400px) {
.card {
display: flex;
}
}
/* Container Queries対応ブラウザ */
@supports (container-type: inline-size) {
.card-container {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
display: flex;
}
}
}
まとめ
Container Queriesを使うべき理由
-
コンポーネントの再利用性が劇的に向上
- 配置場所に依存しないスタイル
- 同じコンポーネントをどこにでも使える
-
メディアクエリより柔軟
- viewport ではなく親要素の幅で判断
- より細かい制御が可能
-
コンポーネント指向の設計に必須
- React/Vue などのコンポーネントフレームワークと相性抜群
- デザインシステムの構築が容易に
-
もう安心して使える
- 主要ブラウザで2年以上サポート
- パフォーマンスも問題なし
今日から使えるチェックリスト
-
カードコンポーネントを
@containerで実装してみる - メディアクエリを Container Queries に置き換えられる箇所を探す
-
コンテナ単位(
cqwなど)を使ってフォントサイズを調整してみる - デザインシステムに Container Queries を組み込む
Container Queries は、レスポンシブデザインの新しいスタンダードです。
ぜひ今日から使ってみてください!
Discussion