CSSコンテナクエリ実戦:5つのパターンでコンポーネントレベルレスポンシブレイアウトを構築

前端工程

コンテナクエリ:レスポンシブデザインのパラダイムシフト

メディアクエリ(@media)はビューポート幅に基づいて応答——同じコンポーネントがサイドバーとメインコンテンツ領域で同じ表示になり、自身のコンテナサイズに適応できない。**CSSコンテナクエリ(@container)**はコンポーネントを親コンテナのサイズに応答させ、真の「コンポーネントレベルレスポンシブ」を実現。2026年、Container Queriesはすべての主要ブラウザでサポート、Container Query Units(cqw/cqh)が新しいレスポンシブ単位となり、コンポーネントライブラリはもうブレークポイントPropsを必要としません。

本記事では5つのコアパターンから、コンテナ定義→コンポーネントクエリ→コンテナユニット→ネストされたコンテナ→実践コンポーネントのフルパイプライン実戦を解説します。


コア概念

概念 説明
@container コンテナサイズに基づくCSSクエリルール
container-type コンテナタイプを定義(inline-size/size/normal)
container-name コンテナに名前を付け、正確なクエリに使用
cqw/cqh コンテナクエリ幅/高さ単位(1cqw = コンテナ幅の1%)
container container-typeとcontainer-nameの短縮形
inline-size インライン方向(幅)のみクエリするコンテナタイプ
size 幅と高さの両方をクエリするコンテナタイプ
style() コンテナCSSカスタムプロパティに基づくクエリ

問題分析:コンテナクエリの5つの課題

  1. コンテナタイプの選択:inline-size vs sizeのパフォーマンスへの影響
  2. ネストされたコンテナ:多層コンテナクエリの優先度とパフォーマンス
  3. フォールバック戦略:コンテナクエリ非対応ブラウザの段階的低下
  4. メディアクエリとの連携:コンテナクエリとメディアクエリの使い分け
  5. コンテナクエリユニット:cqw/cqhとrem/vwの混用問題

ステップバイステップ:5つのコンテナクエリパターン

パターン1:基本コンテナクエリ

/* コンテナを定義 */
.card-container {
  container-type: inline-size;
  container-name: card;
}

/* デフォルト:狭いコンテナ */
.card {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 1rem;
}

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

.card-content {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* 広いコンテナ:水平レイアウト */
@container card (min-width: 400px) {
  .card {
    flex-direction: row;
    align-items: center;
  }

  .card-image {
    width: 200px;
    aspect-ratio: 1;
    flex-shrink: 0;
  }

  .card-content {
    flex: 1;
  }
}

/* より広いコンテナ:大型カード */
@container card (min-width: 600px) {
  .card {
    padding: 1.5rem;
    gap: 1.5rem;
  }

  .card-image {
    width: 280px;
  }

  .card-title {
    font-size: 1.5rem;
  }
}

パターン2:コンテナクエリユニット

/* コンテナクエリユニットでフローティポグラフィ */
.text-container {
  container-type: inline-size;
}

.fluid-heading {
  font-size: clamp(1.2rem, 5cqw, 3rem);
  line-height: 1.2;
}

.fluid-body {
  font-size: clamp(0.875rem, 2.5cqw, 1.125rem);
  line-height: 1.6;
}

.fluid-spacing {
  padding: clamp(0.75rem, 3cqw, 2rem);
  gap: clamp(0.5rem, 2cqw, 1.5rem);
}

/* コンテナクエリ+clampでブレークポイントフリー */
.adaptive-grid {
  container-type: inline-size;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
  gap: clamp(0.75rem, 2cqw, 1.5rem);
}
/* レスポンシブタイポグラフィシステム */
:root {
  --font-scale: 1;
}

.typography-container {
  container-type: inline-size;
}

@container (min-width: 300px) {
  .typography-container { --font-scale: 1.1; }
}

@container (min-width: 500px) {
  .typography-container { --font-scale: 1.25; }
}

@container (min-width: 700px) {
  .typography-container { --font-scale: 1.4; }
}

.responsive-text {
  font-size: calc(1rem * var(--font-scale));
}

パターン3:名前付きコンテナとネスト

/* 名前付きコンテナ:正確なクエリターゲティング */
.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}

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

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

/* サイドバー内のナビゲーション */
@container sidebar (min-width: 250px) {
  .nav-item {
    flex-direction: row;
    gap: 0.75rem;
  }

  .nav-icon {
    width: 20px;
  }

  .nav-label {
    display: block;
    font-size: 0.875rem;
  }
}

@container sidebar (max-width: 249px) {
  .nav-item {
    flex-direction: column;
    align-items: center;
  }

  .nav-label {
    font-size: 0.75rem;
  }
}

/* ネストされたコンテナ:sidebar内のwidget */
@container widget (min-width: 200px) {
  .widget-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@container widget (max-width: 199px) {
  .widget-grid {
    grid-template-columns: 1fr;
  }
}

パターン4:コンテナクエリ+コンポーネントライブラリ

/* コンポーネントライブラリ:ブレークポイントPropsなしのレスポンシブコンポーネント */
.btn-container {
  container-type: inline-size;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  border-radius: 0.375rem;
  font-size: 0.875rem;
  white-space: nowrap;
}

.btn-icon {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
}

.btn-label {
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 狭いコンテナ:アイコンのみ */
@container (max-width: 100px) {
  .btn {
    padding: 0.5rem;
  }

  .btn-label {
    display: none;
  }
}

/* 中程度のコンテナ:アイコン+短いテキスト */
@container (min-width: 101px) and (max-width: 200px) {
  .btn {
    padding: 0.5rem 0.75rem;
  }

  .btn-label {
    max-width: 6ch;
  }
}

/* 広いコンテナ:フル表示 */
@container (min-width: 201px) {
  .btn {
    padding: 0.5rem 1.25rem;
  }
}
/* レスポンシブフォームコンポーネント */
.form-field-container {
  container-type: inline-size;
}

.form-field {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}

@container (min-width: 400px) {
  .form-field {
    flex-direction: row;
    align-items: center;
  }

  .form-label {
    width: 120px;
    flex-shrink: 0;
    text-align: right;
  }

  .form-input {
    flex: 1;
  }
}

パターン5:コンテナスタイルクエリ(@container style())

/* カスタムプロパティに基づくスタイルクエリ */
.theme-container {
  container-type: inline-size;
  --theme: light;
}

.theme-container[data-theme="dark"] {
  --theme: dark;
}

/* テーマ変数に基づいてスタイルを切り替え */
@container style(--theme: dark) {
  .themed-card {
    background: #1f2937;
    color: #f9fafb;
    border-color: #374151;
  }

  .themed-card:hover {
    background: #111827;
  }
}

@container style(--theme: light) {
  .themed-card {
    background: #ffffff;
    color: #111827;
    border-color: #e5e7eb;
  }

  .themed-card:hover {
    background: #f9fafb;
  }
}

/* レイアウト変数に基づく切り替え */
.layout-container {
  container-type: inline-size;
  --layout: compact;
}

.layout-container[data-layout="comfortable"] {
  --layout: comfortable;
}

@container style(--layout: compact) {
  .list-item {
    padding: 0.5rem;
    gap: 0.5rem;
  }
}

@container style(--layout: comfortable) {
  .list-item {
    padding: 1rem;
    gap: 1rem;
  }
}

よくある落とし穴

落とし穴1:container-typeの設定忘れ

/* ❌ 間違い:コンテナタイプ未定義、@containerが機能しない */
.parent { }
@container (min-width: 400px) { .child { ... } }

/* ✅ 正しい:先にコンテナタイプを定義 */
.parent { container-type: inline-size; }
@container (min-width: 400px) { .child { ... } }

落とし穴2:sizeタイプのパフォーマンス問題

/* ❌ 間違い:高さクエリが不要なのにsizeを使用 */
.container { container-type: size; }

/* ✅ 正しい:幅のみのクエリにはinline-sizeを使用 */
.container { container-type: inline-size; }

落とし穴3:コンテナクエリとメディアクエリの競合

/* ❌ 間違い:コンテナクエリとメディアクエリが互いに上書き */
@media (min-width: 768px) { .card { flex-direction: row; } }
@container (min-width: 400px) { .card { flex-direction: column; } }

/* ✅ 正しい:メディアクエリはレイアウトフレーム、コンテナクエリはコンポーネント内部 */
@media (min-width: 768px) { .sidebar { width: 300px; } }
@container (min-width: 400px) { .card { flex-direction: row; } }

落とし穴4:コンテナクエリユニットのオーバーフロー

/* ❌ 間違い:cqwにclamp制限なし、極端なケースでオーバーフロー */
.title { font-size: 5cqw; }

/* ✅ 正しい:clampで範囲を制限 */
.title { font-size: clamp(1rem, 5cqw, 3rem); }

落とし穴5:ネストされたコンテナクエリのターゲット間違い

/* ❌ 間違い:名前なしコンテナ、間違った祖先にマッチ */
.outer { container-type: inline-size; }
.inner { container-type: inline-size; }
@container (min-width: 400px) { .item { ... } }  /* innerにマッチ */

/* ✅ 正しい:名前付きコンテナで正確にマッチ */
.outer { container-type: inline-size; container-name: outer; }
.inner { container-type: inline-size; container-name: inner; }
@container outer (min-width: 400px) { .item { ... } }

エラートラブルシューティング

# エラーメッセージ 原因 解決方法
1 @container not working container-type未設定 container-type: inline-sizeを追加
2 cqw unit not applied ブラウザが非対応 ブラウザバージョンを確認、フォールバックを追加
3 Container query matches wrong element ネストされたコンテナが名前なし container-nameで区別
4 Layout shift on container change コンテナサイズ変更によるリフロー transitionでスムーズに変更
5 Performance degradation container-type: sizeのオーバーヘッド inline-sizeに変更
6 style() query not supported ブラウザがスタイルクエリ非対応 data-attributeで代替
7 Container query ignored in Shadow DOM Shadow DOM境界の問題 Shadow DOM内でコンテナを定義
8 Flex item container not queried Flex子要素の幅が不確定 min-width: 0を設定
9 Container query breaks grid Grid子要素のコンテナ幅が不確定 minmax()で制約
10 Safari container query bug Safari特定バージョンのバグ Safariを更新、またはpolyfillを追加

高度な最適化

  1. ブレークポイントフリーデザイン:固定ブレークポイントの代わりにcqw+clampでフロイドレスポンシブを実現
  2. コンポーネントToken:コンテナクエリ+CSSカスタムプロパティでコンポーネントレベルテーマ
  3. コンテナクエリPolyfill:Container Query Polyfillで古いブラウザをサポート
  4. PostCSS変換:PostCSSプラグインでメディアクエリフォールバックを自動生成
  5. デザインシステム統合:コンポーネントライブラリでブレークポイントPropsの代わりにコンテナクエリを統一使用

比較分析

次元 コンテナクエリ メディアクエリ Element Queries JS Resize
クエリ対象 親コンテナ ビューポート 要素自身 要素自身
CSSネイティブ
パフォーマンス ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐
コンポーネントレベル
ネスト対応
ブラウザサポート 主要 すべて Polyfill必要 すべて

まとめ:CSSコンテナクエリにより、レスポンシブデザインが「ページレベル」から「コンポーネントレベル」へ進化——コンポーネントはビューポート幅に依存せず、自身のコンテナ空間に適応。inline-sizeコンテナ+名前付きコンテナ+cqwユニットの三位一体で、コンポーネントライブラリはブレークポイントPropsを完全に排除。2026年、コンテナクエリはレスポンシブデザインの新標準となり、メディアクエリと役割分担:メディアクエリはページレイアウト、コンテナクエリはコンポーネント内部を管理。


オンラインツール推奨

ブラウザローカルツールを無料で試す →

#容器查询#响应式设计#CSS Container Query#组件级响应#2026#前端工程