CSS Container Queries 実践ガイド ― コンポーネント単位で考えるレスポンシブ設計

メディアクエリでは無理だった「同じカードが、置き場所によって違うレイアウトになる」を実現する。コンテナクエリは、現場のレスポンシブ設計を一段引き上げる新しい武器です。

メディアクエリだけでは届かない場所がある

「このカードコンポーネント、サイドバーに置くと窮屈で崩れるんですよ」——現場でこういう相談、よくあります。同じカードでも、ヒーロー直下のメイン領域では横並び、サイドバーでは縦積み、と振る舞いを変えたい。けれどメディアクエリは画面幅しか見てくれません。「画面が広いのに、置かれている枠は狭い」というギャップを埋める手段がなかった。

ここで登場するのが @container、いわゆるコンテナクエリです。判断基準が「画面幅」から「親コンテナの幅」に変わる、それだけのこと。でもこの一歩が、レスポンシブ設計の考え方を根っこから変えます。コンポーネントが置き場所を意識して、自分で形を変える——そんな世界が、もう普通のCSSで書けるようになりました。

📌 ポイント
メディアクエリは「ページ全体のレイアウト」を切り替えるのに向いていて、コンテナクエリは「コンポーネント単位の見た目」を切り替えるのに向いています。役割が違うので、どちらかが要らなくなるわけではありません。両方を使い分けるのが現場のスタイルです。

最小構成:containerを宣言して、@containerで分岐する

コンテナクエリの使い方は、慣れるとシンプルです。まず親要素を「コンテナ」に指定して、そのあと子要素のスタイルを @container で書き分けるだけ。順序はこの2ステップです。

最小限の実装パターン
/* 1. 親をコンテナとして宣言 */
.card {
  container-type: inline-size;
  container-name: card;
}

/* 2. コンテナの幅で子のスタイルを切り替える */
@container card (min-width: 480px) {
  .card__inner {
    flex-direction: row;
  }
}

container-type: inline-size は「横方向のサイズを基準にコンテナとして扱う」という宣言。container-name は省略してもいいのですが、複数のコンテナがネストする現場では名前を付けておくほうが事故が減ります。

▶ Live Demo — 枠のサイズを変えるとレイアウトが変わる

下の枠は右下からドラッグでリサイズできます。幅が480pxを超えると、カードが横並びレイアウトに切り替わります。

↔ ドラッグでリサイズ

コンテナで切り替わるカード

このカードは画面幅ではなく、親の枠の幅で形を変えています。サイドバーでもメインでも、置いた場所に合わせて自然に振る舞います。

このデモがコンテナクエリの本質です。ブラウザ全体のサイズは変えていないのに、カード自身の置かれた枠の幅でレイアウトが切り替わる。「コンポーネントが置き場所を理解する」という感覚、つかめてきたのではないでしょうか。

現場でよくあるパターン:同じカードを違う列数で並べる

実務で一番ありがたみを感じるのは、グリッド内に並んだカードたちです。1列のときと2列のときで、各カードの中身の見せ方を変えたい——これがメディアクエリだと、グリッドの列数とカードの内側のスタイルを別々に管理することになって、意外と破綻しがちでした。

❌ メディアクエリだけで頑張る場合
/* 画面幅で全部判定するので、サイドバー内に置かれると破綻する */
@media (min-width: 768px) {
  .card__inner { flex-direction: row; }
}
✅ コンテナクエリで「カード自身が判断する」
.card { container-type: inline-size; }

@container (min-width: 480px) {
  .card__inner { flex-direction: row; }
  .card__title { font-size: 22px; }
}
▶ Live Demo — 1列/2列で同じカードが自動で形を変える

画面幅が広がると2列レイアウトに変化。各カードの幅が480pxを下回るので、自動で縦積みに戻ります。「画面が広い=横並び」ではないのがポイント。

カードA

2列に並ぶと枠が狭くなるので、自動で縦積みに切り替わります。

カードB

同じカードでも、置かれた場所の広さによって振る舞いが変わります。

大事なのは「カード側のCSSは1つだけ」という点です。ページ側がどう並べようと、カード自身が自分の幅を見て判断してくれる。再利用しやすいコンポーネントを書く、というモダンなフロントエンドの考え方とぴったり噛み合います。

💡 現場の経験則
レイアウト用のメディアクエリ(ページ全体の段組み)は今まで通り、コンポーネントの中身はコンテナクエリ。この役割分担で書くと、後からデザインが変更されてもコンポーネントを別の場所に持っていきやすくなります。

応用:cqi 単位でフォントサイズも追従させる

コンテナクエリには、もう一つ嬉しい機能があります。cqi(コンテナの inline サイズの1%)という単位です。これを使うと、コンテナの幅に応じてフォントサイズなどを滑らかに変化させられます。vw の「コンテナ版」と思えばイメージしやすいはず。

clamp() と cqi の組み合わせ
.box {
  container-type: inline-size;
}
.box__title {
  /* 最小18px、コンテナ幅の6%、最大36px の範囲で滑らかに */
  font-size: clamp(18px, 6cqi, 36px);
}
▶ Live Demo — タイトルがコンテナ幅に追従する

下の枠を右下からリサイズしてみてください。タイトルの文字サイズが、画面幅ではなく「枠の幅」に合わせて伸び縮みします。

↔ ドラッグでリサイズ

枠幅で大きさが決まるタイトル

これがコンテナ単位 cqi の力です。配置場所に応じて、タイポグラフィが自然にスケールします。

関連する単位として、cqw(width)、cqh(height)、cqb(block)、cqmincqmax もあります。実務でよく使うのは cqi ひとつ、というのが正直なところ。横書きサイトなら cqi = cqw なので、まずはこれだけ覚えれば十分です。

⚠️ 注意
container-type: inline-size を指定したコンテナは、内部に対して新しい「包含ブロック」を作るような扱いになります。position: fixed の子要素や、子要素の height: 100% がうまく効かないケースがあるので、コンテナにする要素は意図して選ぶようにしましょう。乱発禁止です。

導入のコツとブラウザ対応

コンテナクエリは2023年に主要ブラウザがそろって対応しました。今ではChrome、Safari、Firefox、Edgeすべての安定版で問題なく動きます。古いブラウザを切り捨てない案件でも、@supports を使えば段階的に導入できます。

@supports で安全に段階導入
/* デフォルトは縦積み(コンテナクエリ非対応でも崩れない) */
.card__inner { flex-direction: column; }

@supports (container-type: inline-size) {
  .card { container-type: inline-size; }

  @container (min-width: 480px) {
    .card__inner { flex-direction: row; }
  }
}

もう一つ、現場で大事なのが「どの要素をコンテナにするか」の設計です。むやみに全部のコンポーネントに container-type を付けると、レイアウトの基準が増えすぎて逆にカオスになります。再利用するコンポーネントの「最も外側のラッパー」だけをコンテナにする、というルールで運用するとシンプルに保てます。

あとはネーミング。チームで書くなら container-name を付けて、たとえば cardmediasidebar のように役割で命名しておくと、あとから追跡しやすくなります。匿名コンテナでも動きますが、ネストしたときに「どのコンテナを見ているのか」が曖昧になって事故りやすいので、名前は付ける派をおすすめします。

まとめ

  • コンテナクエリは「画面幅」ではなく「親コンテナの幅」でスタイルを切り替えるしくみで、コンポーネント単位のレスポンシブ設計に向いている。
  • 使い方は2ステップ。親に container-type: inline-size を指定し、@container (min-width: ...) で子のスタイルを書き分ける。
  • 同じカードを「メイン領域」と「サイドバー」の両方に置くようなUIで、最大の効果を発揮する。
  • メディアクエリと役割を分担するのが現場のスタイル。ページ全体の段組みはメディアクエリ、コンポーネント内部はコンテナクエリ。
  • cqi 単位を使えば、フォントサイズや余白をコンテナ幅に応じて滑らかに変化させられる。
  • container-type は新しい包含ブロックを作るため、position: fixed の挙動などに注意。乱発せず、再利用するコンポーネントの最外殻だけに付けるのがコツ。
  • 主要ブラウザは対応済み。古い環境を考慮するなら @supports でフォールバックを書けば安心して導入できる。