CSS Container Queries 完全ガイド|@container・size vs inline-size・コンポーネント単位レスポンシブ

(更新: 2026年6月22日 ) CSS Container Queries レスポンシブ フロントエンド UI
結論

Container Queries(@container は、ビューポート幅ではなく親コンテナのサイズを基準にスタイルを切り替えるCSS仕様だ。2026年のフロントエンド実務では container-type: inline-size を親に付け、子で @container を書く のがベースライン。Media Query はページ骨格、Container Query はカード・パネル・ウィジェット——役割を分けると、Design System の再利用性が一気に上がる。

Container Queries とは — なぜ Media Query だけでは足りないのか

2010年代のレスポンシブWebは @media (min-width: 768px) を軸に設計された。スマホ・タブレット・PCという画面サイズの3段階で、ページ全体のグリッドやナビゲーションを切り替えるのは理にかなっていた。

しかし2020年代のUIはもっと複雑だ。

  • ダッシュボード — サイドバーを開閉するとメインカラム幅だけが変わる。カードコンポーネントは「画面幅」ではなく「割り当てられた列幅」で見た目を決めたい
  • CMSの埋め込みブロック — 同じ「記事カード」が1カラム記事・2カラムサイドバー・Related Posts グリッドに再利用される
  • Design System — Storybook 上のプレビュー枠が 320px でも 1200px でも、コンポーネント単体で正しく崩れないことが求められる

Media Query は常にビューポート(またはルート要素)を参照する。親の が 280px しかなくても、ビューポートが 1440px なら の「PC向けスタイル」が当たってしまう——これが 「コンポーネントが親に合わせて伸縮しない」 問題の正体だ。

CSS Container Queries はこの問題を、コンポーネントの直近のコンテナ祖先のサイズに基づいてスタイルを適用することで解決する。仕様上のキーワードは次の3つだ。

プロパティ / 構文役割
container-type要素を「サイズクエリの対象コンテナ」として宣言する
container-name名前付きコンテナとして @container sidebar のように参照する
@containerコンテナサイズに応じた条件分岐(Media Query のコンテナ版)

Container Queries Level 1 は 2022〜2023年に主要ブラウザへ実装され、2026年時点では新規プロジェクトのデフォルト選択肢になっている。

2026年ベースライン — ブラウザ対応と採用判断

2026年6月時点の実装状況を整理する。

環境 Container Queries 対応
Chrome / Edge 105+(2022年9月〜)。DevTools に Container overlay あり
Firefox 110+(2023年2月〜)。@container / container-type 完全対応
Safari / iOS 16.0+(2022年9月〜)。macOS Ventura 以降
Samsung Internet Chromium 系。Chrome と同等
IE11 / レガシー WebView 非対応。@supports フォールバック必須

採用の目安

  • B2C のモダンブラウザ向けサービス → Container Queries を前提に設計して問題なし
  • 社内ツールで Chrome 固定 → 即日導入可
  • 政府・金融系で IE 互換要件 → Progressive Enhancement(後述のフォールバック節参照)

Can I use 上の「Baseline Widely available」に該当する機能として、2026年のフロントエンド求人・Design System ガイドでも @container当たり前の語彙になっている。未対応ブラウザ向けの @media フォールバックは「保険」として残しつつ、主設計は Container Query 基準で進めるのが合理的だ。

35
Media Query のみ: 35
Media Query のみ
78
Media + Container 併用: 78
Media + Container 併用
92
Container 主設計: 92
Container 主設計

※ 値は「コンポーネントの再利用性スコア(相対値・説明用)」として記載。実プロジェクトでは Design System の成熟度に依存する。

最小構成 — container-type と @container の基本

Container Query を動かすには 2ステップ だけ覚えればよい。

  1. 親(コンテナ)container-type を指定する
  2. 子(コンポーネント内)@container ルールを書く

基本例:横並び ↔ 縦積みカード

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Container Query 基本例</title>
  <style>
    /* ページレイアウト — Media Query で骨格を決める */
    .page {
      display: grid;
      grid-template-columns: 1fr;
      gap: 1rem;
      padding: 1rem;
      max-width: 1200px;
      margin: 0 auto;
    }

    @media (min-width: 900px) {
      .page {
        grid-template-columns: 280px 1fr;
      }
    }

    /* サイドバー列 — ここが「コンテナ」になる */
    .sidebar-slot {
      container-type: inline-size;
      container-name: sidebar;
    }

    /* カードコンポーネント — ビューポートではなく sidebar 幅を見る */
    .profile-card {
      display: flex;
      flex-direction: column;
      gap: 0.75rem;
      padding: 1rem;
      border: 1px solid #e2e8f0;
      border-radius: 12px;
      background: #ffffff;
    }

    .profile-card__avatar {
      width: 64px;
      height: 64px;
      border-radius: 50%;
      background: linear-gradient(135deg, #6366f1, #8b5cf6);
    }

    .profile-card__body {
      flex: 1;
    }

    .profile-card__name {
      font-size: 1.125rem;
      font-weight: 700;
      margin: 0 0 0.25rem;
    }

    .profile-card__bio {
      font-size: 0.875rem;
      color: #64748b;
      margin: 0;
      line-height: 1.5;
    }

    /* コンテナ幅が 360px 以上なら横並びレイアウトへ */
    @container sidebar (min-width: 360px) {
      .profile-card {
        flex-direction: row;
        align-items: center;
      }

      .profile-card__avatar {
        width: 80px;
        height: 80px;
      }
    }

    /* メインカラム用の別コンテナ */
    .main-slot {
      container-type: inline-size;
      container-name: main;
    }

    @container main (min-width: 480px) {
      .profile-card {
        flex-direction: row;
        align-items: center;
      }
    }
  </style>
</head>
<body>
  <div class="page">
    <aside class="sidebar-slot">
      <article class="profile-card">
        <div class="profile-card__avatar" aria-hidden="true"></div>
        <div class="profile-card__body">
          <h2 class="profile-card__name">山田 太郎</h2>
          <p class="profile-card__bio">フロントエンドエンジニア。Container Queries でコンポーネント設計を研究中。</p>
        </div>
      </article>
    </aside>
    <main class="main-slot">
      <article class="profile-card">
        <div class="profile-card__avatar" aria-hidden="true"></div>
        <div class="profile-card__body">
          <h2 class="profile-card__name">同じ .profile-card クラス</h2>
          <p class="profile-card__bio">メインカラムでは幅に余裕があるため、@container main の閾値で横並びになる。</p>
        </div>
      </article>
    </main>
  </div>
</body>
</html>

ポイントは 同じ .profile-card クラスが、親コンテナの幅に応じて異なる見た目になる ことだ。ビューポートが 1440px でも、サイドバー内カードは縦積みのまま——Media Query だけでは実現できなかった挙動だ。

container-name は省略可能

単一の @container だけ使うなら container-name は不要だ。1ページ内に複数種類のコンテナ(sidebar / main / widget)があり、同じ子コンポーネントがどのコンテナのクエリに反応するかを限定したいときに名前を付ける。

container-type: inline-size vs size — 使い分けの核心

container-type には主に inline-sizesize がある(normal はクエリ対象外)。ここを誤ると「クエリが効かない」「レイアウトが崩れる」「パフォーマンスが悪化する」のいずれかが起きる。

container-type 特徴と向いている用途
inline-size インライン方向(横書きなら幅)のみ追跡。高さは子の内容に委ねる。大半のUIコンポーネント向け。再計算コスト低
size 幅と高さの両方を追跡。aspect-ratio 連動、画像トリミング切替など高さ依存レイアウト向け。子の % height が解決しやすくなる代わりコスト高
normal(デフォルト) サイズクエリ非対応。@container はこの祖先をスキップする

inline-size がデフォルト選択である理由

横書き(writing-mode: horizontal-tb)の日本語Webページでは、インライン方向 = 水平方向 = 幅 だ。カードの「1列 ↔ 2列」「アイコンを上 ↔ 横」といった典型的なレスポンシブは幅の閾値だけで足りる。

は CSS Containment の inline-size containment を有効にする。コンテナの高さは子要素の intrinsic height に依存するため、親子間の循環依存(サイズの鶏と卵)が起きにくい。Layout エンジンにとっても、再計算範囲が限定されやすい。

size が必要になるケース

次のような要件では size を検討する。

  • コンテナの高さが固定height: 300px)で、その中で画像の object-fit: covercontain を切り替えたい
  • 正方形に近いタイルで、幅と高さの両方が閾値を超えたら詳細テキストを表示する
  • チャートやマップのオーバーレイで、パネルの縦横比に応じて凡例の位置を変える
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>container-type: size の例</title>
  <style>
    .gallery {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
      gap: 1rem;
      padding: 1rem;
    }

    .tile {
      container-type: size;
      container-name: tile;
      height: 220px;
      border-radius: 16px;
      overflow: hidden;
      position: relative;
      background: #0f172a;
    }

    .tile__image {
      width: 100%;
      height: 100%;
      object-fit: cover;
      display: block;
    }

    .tile__caption {
      position: absolute;
      inset: auto 0 0 0;
      padding: 0.75rem 1rem;
      background: linear-gradient(transparent, rgba(0, 0, 0, 0.75));
      color: #fff;
      font-size: 0.875rem;
      transform: translateY(100%);
      transition: transform 0.2s ease;
    }

    /* 幅も高さも十分なタイルだけキャプション常時表示 */
    @container tile (min-width: 240px) and (min-height: 200px) {
      .tile__caption {
        transform: translateY(0);
      }

      .tile__image {
        object-fit: cover;
      }
    }

    /* 小さいタイルは contain で全体を見せる */
    @container tile (max-width: 239px), (max-height: 199px) {
      .tile__image {
        object-fit: contain;
        background: #1e293b;
      }
    }
  </style>
</head>
<body>
  <div class="gallery">
    <figure class="tile">
      <img class="tile__image" src="/images/sample-a.webp" alt="サンプル画像 A" width="400" height="300">
      <figcaption class="tile__caption">高解像度タイル — キャプション常時表示</figcaption>
    </figure>
    <figure class="tile" style="height: 160px;">
      <img class="tile__image" src="/images/sample-b.webp" alt="サンプル画像 B" width="400" height="300">
      <figcaption class="tile__caption">低いタイル — object-fit: contain</figcaption>
    </figure>
  </div>
</body>
</html>
size は乱用しない

container-type: size をページ全体の <main> に付けると、子孫すべてのレイアウト計算がコンテナサイズ解決に縛られ、Layout イベントが増える可能性がある。Above-the-fold の広い領域より、個別タイル・パネル単位に閉じ込めるのが安全だ。計測の考え方は クリティカルレンダリングパス最適化 を参照。

@container 構文 — 条件式と Size Query の書き方

Container Query Level 1 では、Media Query と似た Size Query 構文が使える。

/* 基本形 */
@container (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 120px 1fr;
    gap: 1rem;
  }
}

/* 名前付きコンテナ */
@container sidebar (min-width: 280px) {
  .nav {
    flex-direction: row;
    gap: 0.5rem;
  }
}

/* 複数条件 */
@container (min-width: 320px) and (max-width: 599px) {
  .card {
    padding: 1rem;
    font-size: 0.9375rem;
  }
}

/* 範囲構文(新しい記法) */
@container (400px <= width <= 799px) {
  .card {
    flex-direction: row;
    align-items: center;
  }
}

/* 高さ条件 — container-type: size が必要 */
@container (min-height: 300px) {
  .panel {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
  }
}

/* aspect-ratio */
@container (aspect-ratio > 3/2) {
  .hero {
    flex-direction: row;
    align-items: center;
  }
}

クエリ可能な軸

必要な container-type
width / inline-sizeinline-size または size(min-width: 480px)
height / block-sizesize のみ(min-height: 200px)
aspect-ratiosize(aspect-ratio > 1/1)
orientationsize(orientation: landscape)

inline-size だけのコンテナで min-height を指定してもマッチしない。エディタの lint や DevTools が警告を出さないこともあるため、height 条件を書く前に container-type を確認する習慣が大切だ。

コンテナクエリ単位 — cqw, cqi, cqb, cqmin, cqmax

Media Query 時代の vw / vh はビューポート基準だ。Container Query では コンテナ基準の長さ単位 が使える。

単位意味
cqwコンテナ幅の 1%
cqhコンテナ高さの 1%
cqiコンテナインラインサイズの 1%
cqbコンテナブロックサイズの 1%
cqmincqicqb の小さい方
cqmaxcqicqb の大きい方

Fluid Typography をコンテナ基準で

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

.article-card__title {
  /* コンテナ幅 280px で約 1.25rem、560px で約 1.75rem */
  font-size: clamp(1.25rem, 0.5rem + 4cqi, 1.75rem);
}

.article-card__excerpt {
  font-size: clamp(0.875rem, 0.75rem + 1cqi, 1rem);
  line-height: 1.6;
}

@container (min-width: 520px) {
  .article-card__meta {
    display: flex;
    gap: 1rem;
  }
}

vw ベースの Fluid Typography は、サイドバー内の狭いカードでも「画面がデスクトップだから大きい文字」になってしまう。cqi + clamp() はコンポーネントの実占有幅に追従するため、Design System のタイポグラフィトークンをコンテキスト非依存に保てる。

rem との併用

アクセシビリティのため、cqi だけで fontsize を決めず clamp(1rem, 0.5rem + 3cqi, 1.5rem) のように rem を下限に含める。ユーザー拡大(ブラウザズーム)との兼ね合いは Fluid Typography と同じ原則が適用される。

Media Query との役割分担 — ページ vs コンポーネント

Container Queries は Media Queries を置き換えるものではない。レイヤーを分ける。

Media Query (@media) Container Query (@container)
参照対象 ビューポート / ユーザー環境
参照対象 直近の名前付き・無名コンテナ祖先
向いている用途 グリッド列数、固定ヘッダー、print、prefers-reduced-motion
向いている用途 カードレイアウト、ナビ折りたたみ、テーブル→カード変換
Design System ページテンプレート・ルート変数
Design System Button / Card / DataTable 等の再利用コンポーネント

推奨パターン:外側 Media、内側 Container

/* ページ骨格 — Media Query */
.app-shell {
  display: grid;
  grid-template-columns: 1fr;
}

@media (min-width: 1024px) {
  .app-shell {
    grid-template-columns: 240px 1fr 320px;
  }
}

/* 各カラムスロット — Container */
.app-shell__aside,
.app-shell__main,
.app-shell__panel {
  container-type: inline-size;
}

/* コンポーネント — Container Query のみ */
@container (min-width: 400px) {
  .data-table {
    display: table;
  }
}

@container (max-width: 399px) {
  .data-table {
    display: block;
  }

  .data-table tr {
    display: flex;
    flex-direction: column;
    border: 1px solid #e2e8f0;
    margin-bottom: 0.75rem;
    border-radius: 8px;
    padding: 0.75rem;
  }
}

ページが 3カラムになっても、右パネル(320px)内の DataTable はコンテナ幅が狭いためカード表示のまま——これがコンポーネントレベルレスポンシブの理想形だ。

実践例1 — 商品カードグリッド(eコマース)

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>商品カード — Container Queries</title>
  <style>
    :root {
      --color-border: #e5e7eb;
      --color-muted: #6b7280;
      --color-accent: #2563eb;
    }

    .product-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
      gap: 1rem;
      padding: 1rem;
    }

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

    .product-card__inner {
      display: flex;
      flex-direction: column;
      height: 100%;
      border: 1px solid var(--color-border);
      border-radius: 12px;
      overflow: hidden;
      background: #fff;
    }

    .product-card__media {
      aspect-ratio: 4 / 3;
      background: #f3f4f6;
      display: flex;
      align-items: center;
      justify-content: center;
      color: var(--color-muted);
      font-size: 0.75rem;
    }

    .product-card__content {
      padding: 0.75rem;
      display: flex;
      flex-direction: column;
      gap: 0.5rem;
      flex: 1;
    }

    .product-card__title {
      font-size: clamp(0.875rem, 0.5rem + 2.5cqi, 1rem);
      font-weight: 600;
      margin: 0;
      line-height: 1.4;
    }

    .product-card__price {
      font-size: 1.125rem;
      font-weight: 700;
      color: var(--color-accent);
      margin: 0;
    }

    .product-card__actions {
      margin-top: auto;
      display: flex;
      flex-direction: column;
      gap: 0.5rem;
    }

    .product-card__button {
      padding: 0.5rem 0.75rem;
      border-radius: 8px;
      border: none;
      background: var(--color-accent);
      color: #fff;
      font-size: 0.875rem;
      cursor: pointer;
    }

    .product-card__button--secondary {
      background: transparent;
      color: var(--color-accent);
      border: 1px solid var(--color-accent);
    }

    /* カード幅が広い — 横並び + アクション横並び */
    @container product (min-width: 280px) {
      .product-card__inner {
        flex-direction: row;
      }

      .product-card__media {
        width: 40%;
        aspect-ratio: auto;
        min-height: 140px;
      }

      .product-card__content {
        width: 60%;
        padding: 1rem;
      }

      .product-card__actions {
        flex-direction: row;
      }

      .product-card__button {
        flex: 1;
      }
    }

    /* さらに広い — 2行説明を表示 */
    @container product (min-width: 360px) {
      .product-card__description {
        display: block;
        font-size: 0.8125rem;
        color: var(--color-muted);
        line-height: 1.5;
        margin: 0;
      }
    }

    .product-card__description {
      display: none;
    }
  </style>
</head>
<body>
  <section class="product-grid" aria-label="おすすめ商品">
    <article class="product-card">
      <div class="product-card__inner">
        <div class="product-card__media">商品画像</div>
        <div class="product-card__content">
          <h3 class="product-card__title">ワイヤレスイヤホン Pro</h3>
          <p class="product-card__description">ノイズキャンセリング搭載。最大30時間再生。</p>
          <p class="product-card__price">¥12,800</p>
          <div class="product-card__actions">
            <button type="button" class="product-card__button">カートに入れる</button>
            <button type="button" class="product-card__button product-card__button--secondary">詳細</button>
          </div>
        </div>
      </div>
    </article>
    <article class="product-card">
      <div class="product-card__inner">
        <div class="product-card__media">商品画像</div>
        <div class="product-card__content">
          <h3 class="product-card__title">USB-C ハブ 7in1</h3>
          <p class="product-card__description">4K HDMI、SD、100W PD対応。</p>
          <p class="product-card__price">¥6,480</p>
          <div class="product-card__actions">
            <button type="button" class="product-card__button">カートに入れる</button>
            <button type="button" class="product-card__button product-card__button--secondary">詳細</button>
          </div>
        </div>
      </div>
    </article>
  </section>
</body>
</html>

grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)) で列数はビューポート依存だが、各カード内部のレイアウト@container product が決める。列が狭いときは縦積み、セルが広がれば横並び——Media Query だけでは「列幅ごとに」制御できなかった領域だ。

実践例2 — ダッシュボードウィジェット

.dashboard {
  display: grid;
  gap: 1rem;
  padding: 1rem;
  grid-template-columns: 1fr;
}

@media (min-width: 768px) {
  .dashboard {
    grid-template-columns: repeat(12, 1fr);
  }

  .dashboard__widget--wide {
    grid-column: span 8;
  }

  .dashboard__widget--narrow {
    grid-column: span 4;
  }
}

.dashboard__widget {
  container-type: inline-size;
  container-name: widget;
  background: #ffffff;
  border: 1px solid #e2e8f0;
  border-radius: 16px;
  padding: 1rem;
  min-height: 120px;
}

.widget-stats {
  display: grid;
  gap: 0.75rem;
  grid-template-columns: 1fr;
}

.widget-stats__item {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 0.5rem 0;
  border-bottom: 1px solid #f1f5f9;
}

.widget-stats__label {
  font-size: 0.8125rem;
  color: #64748b;
}

.widget-stats__value {
  font-size: clamp(1rem, 0.5rem + 3cqi, 1.5rem);
  font-weight: 700;
}

/* ウィジェット幅 320px 以上 — 2列グリッド */
@container widget (min-width: 320px) {
  .widget-stats {
    grid-template-columns: repeat(2, 1fr);
  }

  .widget-stats__item {
    border-bottom: none;
    border-right: 1px solid #f1f5f9;
    padding: 0.75rem;
  }

  .widget-stats__item:nth-child(2n) {
    border-right: none;
  }
}

/* ウィジェット幅 520px 以上 — 4列 + 大きな KPI */
@container widget (min-width: 520px) {
  .widget-stats {
    grid-template-columns: repeat(4, 1fr);
  }

  .widget-stats__item {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.25rem;
    border-right: 1px solid #f1f5f9;
  }

  .widget-stats__item:last-child {
    border-right: none;
  }
}

ダッシュボードでは Grid でウィジェットの占有列を決め、Container Query でウィジェット内部を決める——二段構えが保守しやすい。

実践例3 — ナビゲーションの折りたたみ

<nav class="local-nav" aria-label="セクション内ナビ">
  <ul class="local-nav__list">
    <li><a href="#overview">概要</a></li>
    <li><a href="#spec">仕様</a></li>
    <li><a href="#api">API</a></li>
    <li><a href="#changelog">更新履歴</a></li>
  </ul>
</nav>

<style>
  .local-nav {
    container-type: inline-size;
    container-name: local-nav;
    border-bottom: 1px solid #e2e8f0;
  }

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

  .local-nav__list a {
    display: block;
    padding: 0.625rem 0.75rem;
    text-decoration: none;
    color: #334155;
    border-radius: 8px;
    font-size: 0.9375rem;
  }

  .local-nav__list a:hover {
    background: #f1f5f9;
  }

  @container local-nav (min-width: 480px) {
    .local-nav__list {
      flex-direction: row;
      gap: 0.5rem;
    }

    .local-nav__list a {
      padding: 0.75rem 1rem;
    }
  }

  @container local-nav (min-width: 720px) {
    .local-nav__list {
      justify-content: center;
    }
  }
</style>

ドキュメントサイトで、本文カラムが狭いときは縦積みナビ、幅に余裕があれば横並び——サイドバー付きレイアウトでもメインカラム幅だけで切り替わる。

Style Queries(Container Query Level 4)— 2026年の展望

Size Query だけでなく、カスタムプロパティの値で分岐する Style Query が Level 4 で議論・部分実装されている。

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

@container style(--variant: compact) {
  .card { padding: 0.5rem; }
}

@container style(--variant: spacious) {
  .card { padding: 1.5rem; }
}

2026年時点では Style Query のブラウザ対応は Size Query ほど universal ではない。本番の主軸は Size Query とし、Style Query は「テーマ切り替えをコンテナ単位で」など限定的な実験向けと捉えるのが安全だ。進捗は Can I use と MDN の @container ページで随時確認する。

フレームワークとの統合 — React / Vue / Astro

Container Queries はフレームワーク非依存のCSS機能だ。コンポーネントのルート要素に container-type を付けるだけで動く。

// ProductCard.tsx — React 例
type ProductCardProps = {
  title: string;
  price: number;
  imageUrl: string;
};

export function ProductCard({ title, price, imageUrl }: ProductCardProps) {
  return (
    <article className="product-card">
      <div className="product-card__inner">
        <img className="product-card__media" src={imageUrl} alt="" />
        <div className="product-card__content">
          <h3 className="product-card__title">{title}</h3>
          <p className="product-card__price">¥{price.toLocaleString('ja-JP')}</p>
        </div>
      </div>
    </article>
  );
}
/* ProductCard.module.css またはグローバル CSS */
.product-card {
  container-type: inline-size;
  container-name: product;
}

@container product (min-width: 280px) {
  .product-card__inner {
    display: grid;
    grid-template-columns: 120px 1fr;
    gap: 1rem;
  }
}

Tailwind CSS v3.2+ では @tailwindcss/container-queries プラグイン(v4 では統合方向)で @container ユーティリティが使える。Design Token を CSS 変数で持ち、コンテナ閾値だけ Tailwind の @md: 相当 @lg:@container/ 名前空間で書くチームも増えている。

Astro / MDX ブログでの埋め込み

このポートフォolioのように Astro + MDX では、レイアウトスロット<aside> / <main>)に container-type を付け、MDX 内のコンポーネントは Container Query のみを意識する——コンテンツ作者がビューポートブレークポイントを意識しないで済む。

デバッグ — Chrome DevTools で Container を可視化

1

Chrome DevTools を開き、Elements パネルで container-type が付いた要素を選択する

2

Styles パネル上部の「Container queries」バッジ、または Layout オーバーレイでコンテナ境界を確認する

3

Rendering タブ(Command Palette → Show Rendering)で「Layout Shift Regions」を有効にし、リサイズ時の CLS を確認する

4

@container ルールが効いているか、Computed スタイルで該当プロパティの打ち消し線を確認する

5

container-name の typo や、height 条件と inline-size の組み合わせミスを疑う

よくある原因

症状原因
@container が一切効かない祖先に container-type: normal しかない。または子がコンテナの外にある
height 条件だけ効かないcontainer-type: inline-size のみ。size が必要
意図しない祖先がコンテナ上位の container-type がクエリを横取り。container-name で限定
リサイズでガタつくsize の多用、または JS による inline style の頻繁な変更

パフォーマンス調査では Performance パネルで Layout → Recalculate Style の時間を、Container Query 導入前後で比較する。クリティカルレンダリングパス最適化 で述べたように、Layout の連鎖は LCP 後のインタラクションにも効く。Above-the-fold に container-type: size を大量配置していないかもチェック項目に入れる。

フォールバックと Progressive Enhancement

未対応ブラウザ向けの典型パターンは @supports だ。

/* ベース — Media Query フォールバック(狭い画面想定) */
.responsive-card {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

@media (min-width: 768px) {
  .responsive-card {
    flex-direction: row;
  }
}

/* Container Queries 対応ブラウザ — より正確な制御 */
@supports (container-type: inline-size) {
  .card-slot {
    container-type: inline-size;
  }

  @media (min-width: 768px) {
    /* Media の row 指定を上書き — コンテナ基準に一本化 */
    .responsive-card {
      flex-direction: column;
    }
  }

  @container (min-width: 400px) {
    .responsive-card {
      flex-direction: row;
    }
  }
}

対応ブラウザでは Container Query が優先され、非対応では従来の Media Query が効く。二重定義は保守コストが上がるため、2026年の新規案件ではフォールバックを最小限(単一列固定)に留めるチームが多い。

JavaScript ポリフィルの是非

container-query-polyfill 等の JS ポリフィルは、ResizeObserver ベースで動作し、初期描画後にスタイルが切り替わるFOUCや CLS の原因になりやすい。レガシー要件がなければ、CSS @supports のみに留める方が Web Vitals 的にも安全だ。

アクセシビリティ — Container Queries で変えてよいもの・ダメなもの

Container Queries でレイアウト・視覚的密度を変えるのは問題ない。ただし次は守る。

  • DOM の順序 — 狭いコンテナで display: none しすぎず、スクリーンリーダー用に重要リンクを消さない
  • タップターゲット — 横並びにしたボタンの min-height: 44px はコンテナ幅に関わらず維持
  • prefers-reduced-motion — コンテナ内アニメーションも @media (prefers-reduced-motion: reduce) で無効化
@container (min-width: 360px) {
  .toolbar {
    flex-direction: row;
  }
}

@media (prefers-reduced-motion: reduce) {
  .toolbar * {
    transition: none !important;
  }
}

パフォーマンス設計 — Containment と CRP の関係

container-type: inline-sizelayout + inline-size containment を含む。理論上、コンテナ外への Layout 波及を抑えられる。ただし以下に注意する。

  1. ネストしたコンテナ — 親子両方に container-type があると、リサイズ時に内側から再計算が走る
  2. size のコスト — 高さ解決のため子の intrinsic size を先に計算する必要があり、Layout フェーズが重くなりがち
  3. CRP との関係 — Container Query 自体は追加 HTTP リクエストを発生させない。問題は初回 Layout の複雑さと、JS 連動リサイズ時の Layout Thrashing

クリティカルレンダリングパス最適化 で説明した Layout → Paint のパイプラインにおいて、Container Query は「正しい抽象化のための小さな Layout コスト」になりうる。ページ全体ではなくコンポメント単位に閉じれば、Media Query で無理やり上書きしていた !important 地獄を減らせる——トレードオフとしてはプラスになりやすい。

チェックリスト — 本番投入前の確認項目

項目 確認内容
container-type 基本は inline-size。size は必要最小限か
閾値 320 / 480 / 640 等、Design Token と整合しているか
名前 container-name の typo がないか。意図した祖先のみがクエリ対象か
単位 cqw / cqi と rem の clamp 下限がアクセシブルか
フォールバック @supports または単一列デザインで非対応時も usable か
計測 DevTools で Layout 時間、CLS を確認したか

まとめ — 2026年のコンポーネントレスポンシブ設計

Container Queries は「コンポーネントが置かれた場所のサイズ」に応答するCSSの本命機能だ。

  • ページ骨格@media でグリッド列・グローバル余白・prefers-* を扱う
  • コンポーネント内部 — 親に container-type: inline-size、子に @container を書く
  • 高さも見る rare case — だけ container-type: size
  • タイポグラフィclamp() + cqi でコンテナ連動
  • 品質 — DevTools の Container overlay と CRP 最適化 の Layout 意識

Media Query 時代は「画面サイズ = デバイス」という近似に依存していた。Container Query 時代は「コンポーネントの箱 = レイアウトコンテキスト」と明示できる。Design System・CMS 埋め込み・ダッシュボード——再利用されるUIほど、2026年のベースラインとして @container を標準装備にする価値が高い。

関連記事

この記事は フロントエンド・CSS テーマの一環です。あわせて読むと理解が深まる関連記事をまとめました。

トピック記事
CSS Grid vs FlexboxCSS Grid vs Flexbox|どっちを使う?使い分けと実戦レイアウト
CSSですりガラス(Glassmorphism)を作る方法CSSですりガラス(Glassmorphism)を作る方法|backdrop-filterの使い方
CSSで浮き出るカードを作るCSSで浮き出るカードを作る|きれいなbox-shadowの数値設定
CSSの単位はpxとremどちらが良い?CSSの単位はpxとremどちらが良い?|基準16pxの理由と使い分け
line-height計算 - 読みやすい行間をフォントサイズから算出line-height計算 - 読みやすい行間をフォントサイズから算出
Tailwind CSSでデザインシステムの色を管理する方法Tailwind CSSでデザインシステムの色を管理する方法
色のコントラストチェック色のコントラストチェック|color contrastの使い方と注意点
React SSR Hydration Mismatch 原因と修正React SSR Hydration Mismatch 原因と修正|Next.js・Astro対応ガイド
HTML Popover API 完全ガイドHTML Popover API 完全ガイド|popover・popovertarget・light dismiss・アクセシビリティ
Fluid TypographyFluid Typography|clamp() で可変フォント
content-visibility: auto 完全ガイドcontent-visibility: auto 完全ガイド|遅延レンダリングと長尺ページ最適化 2026
CSS JS Minify 圧縮CSS JS Minify 圧縮|本番向けファイルサイズ削減