メインコンテンツまでスキップ
  • Created:
  • Updated:
  • Author:
    Takeshi Takatsudo

コンテナクエリ

問題

メディアクエリ(media query)はビューポートの幅に応じて反応しますが、コンポーネントのコンテナの幅には対応しません。コンポーネントがサイドバー、モーダル、あるいは制約のあるレイアウトに配置された場合、ビューポートベースのメディアクエリではコンポーネントのレイアウトを実際の利用可能スペースに適応させることができません。AIエージェントはコンポーネントレベルのレスポンシブ対応にほぼ常に @media クエリを使い、コンテナクエリ(container query)を完全に無視してしまいます。

解決方法

CSSコンテナクエリ(@container)を使うと、コンポーネントをビューポートではなく親コンテナのサイズに応じて変化させることができます。これにより、コンポーネントが異なるレイアウトコンテキストで真に再利用可能になります。コンテナクエリは Baseline 2023 であり、すべてのモダンブラウザでサポートされています。

コンテナの設定

親要素を container-type を使ってコンテインメントコンテキスト(containment context)として宣言する必要があります。最も一般的な値は inline-size で、コンテナのインライン方向(水平方向)のサイズに基づくクエリを有効にします。

.card-wrapper {
container-type: inline-size;
}

コンテナへのクエリ

@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 200px 1fr;
}
}

基本的なコンテナクエリ

このデモでは iframe がコンテナの境界として機能します。ビューポートボタンを使って、カードレイアウトがコンテナの幅に応じて変化する様子を確認しましょう。

基本的なコンテナクエリ — カードレイアウトがコンテナ幅に応じて変化

名前付きコンテナ

コンテナがネストされている場合、@container クエリは container-type が設定されている最も近い祖先要素にマッチします。特定のコンテナをターゲットにするには、container-name を使い、クエリ内でその名前を参照します。

.sidebar {
container-type: inline-size;
container-name: sidebar;
}

.main-content {
container-type: inline-size;
container-name: main;
}

/* Only responds to the sidebar container */
@container sidebar (max-width: 300px) {
.nav-list {
flex-direction: column;
}
}

container ショートハンドプロパティで両方を組み合わせることができます:

.sidebar {
container: sidebar / inline-size;
}
名前付きコンテナ — 同じコンポーネントがサイドバーとメインで異なる表示に

コンテナクエリの単位

コンテナクエリ単位(container query unit)は、クエリコンテナの寸法に対する相対値です。コンポーネント内でのフルイドサイジングに便利です。

  • cqw — コンテナの幅の1%
  • cqh — コンテナの高さの1%
  • cqi — コンテナのインラインサイズの1%
  • cqb — コンテナのブロックサイズの1%
  • cqmincqicqb の小さい方
  • cqmaxcqicqb の大きい方
.card-container {
container-type: inline-size;
}

.card__title {
/* 5% of the container's inline size, clamped */
font-size: clamp(1rem, 5cqi, 2rem);
}

.card__body {
/* Padding relative to container width */
padding: 2cqi;
}
コンテナクエリ単位 — テキストと余白がコンテナに応じてスケール

コンテナ幅に適応するカードコンポーネント

よくある実用的なユースケースとして、狭いサイドバー、中幅のグリッドカラム、全幅のメインエリアなど、どのレイアウトコンテキストでも機能するカードコンポーネントがあります。

アダプティブカードグリッド — カードがビューポートではなくコンテナに応じて変化

コンテナクエリとメディアクエリの比較

重要な違い:メディアクエリはビューポートに応じて反応し、コンテナクエリは親コンテナに応じて反応します。このデモでは、同じページ上の幅が異なる2つのコンテナに同一コンポーネントを配置しています。メディアクエリ版はビューポートが変わっていないため両方とも同じ見た目になります。コンテナクエリ版はそれぞれのコンテナに独立して適応します。

コンテナクエリ vs メディアクエリ — 同じコンポーネント、異なるコンテナ

上のデモでは、@container カードはそれぞれ独立して適応します。狭いコンテナ内のカードは縦に積み重なり、広いコンテナ内のカードは横並びになります。@media カードはビューポート(iframe)が 300px より広いため両方とも横並びになります。どちらのカードも実際のコンテナの幅を認識していません。

コード例

レスポンシブカードコンポーネント

.card-container {
container-type: inline-size;
container-name: card;
}

/* Base: stacked layout */
.card {
display: flex;
flex-direction: column;
}

.card__image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}

/* When container is wide enough: horizontal layout */
@container card (min-width: 500px) {
.card {
flex-direction: row;
}

.card__image {
width: 200px;
aspect-ratio: 1;
}
}

/* When container is very wide: add extra spacing */
@container card (min-width: 800px) {
.card {
gap: 2rem;
padding: 2rem;
}

.card__image {
width: 300px;
}
}

コンテナに適応するナビゲーション

.nav-wrapper {
container-type: inline-size;
container-name: nav;
}

.nav-list {
display: flex;
flex-direction: column;
gap: 0.25rem;
list-style: none;
padding: 0;
margin: 0;
}

/* Horizontal layout when container allows */
@container nav (min-width: 600px) {
.nav-list {
flex-direction: row;
gap: 1rem;
}
}

コンテナクエリとコンテナクエリ単位の組み合わせ

.widget-wrapper {
container: widget / inline-size;
}

.widget__title {
font-size: clamp(1rem, 5cqi, 2rem);
}

.widget__body {
padding: clamp(0.5rem, 3cqi, 1.5rem);
}

@container widget (min-width: 400px) {
.widget {
display: grid;
grid-template-columns: auto 1fr;
gap: 1rem;
}
}

AIがよくやるミス

  • コンポーネントレイアウトにメディアクエリを使う: AIエージェントは、コンポーネントがビューポートではなくコンテナに適応する必要がある場合でも、デフォルトで @media クエリを使ってしまいます。
  • container-type を忘れる: 親要素に container-type を設定せずに @container ルールを書いてしまいます。コンテナは明示的に宣言する必要があります。
  • container-type: size を不必要に使う: 高さベースのコンテインメント(size)はレイアウトの問題を引き起こす可能性があります。ほとんどの場合は inline-size を使いましょう。
  • ネストされたコンテナに名前を付けない: コンテナがネストされているとき、container-name を省略すると曖昧さが生じます。@container クエリは最も近い祖先コンテナにマッチします。ネスト時は特定の祖先をターゲットにするためにコンテナに名前を付けましょう。
  • 要素自体にクエリしてしまう: @container クエリは container-type が設定された最も近い祖先をターゲットにするのであって、スタイルを適用する要素自体ではありません。コンテナとスタイル対象の要素は別の要素である必要があります。

使い分け

  • コンポーネントレベルのレスポンシブ対応: 異なるレイアウト幅に配置される可能性のある再利用可能なコンポーネント(カード、ナビゲーション、フォームグループ)に使いましょう。
  • サイドバーとメインコンテンツ: 同じページ上で、同じコンポーネントが広いコンテキストと狭いコンテキストの両方に表示される場合に使いましょう。
  • デザインシステムのコンポーネント: 異なるアプリケーションやレイアウトで再利用するために構築されたコンポーネントに使いましょう。
  • ページレベルのレイアウトには不向き: シングルカラムとマルチカラムのページレイアウト切り替えなど、マクロなレイアウトの関心事には引き続き @media クエリを使いましょう。

参考リンク