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つの課題
- コンテナタイプの選択:inline-size vs sizeのパフォーマンスへの影響
- ネストされたコンテナ:多層コンテナクエリの優先度とパフォーマンス
- フォールバック戦略:コンテナクエリ非対応ブラウザの段階的低下
- メディアクエリとの連携:コンテナクエリとメディアクエリの使い分け
- コンテナクエリユニット: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を追加 |
高度な最適化
- ブレークポイントフリーデザイン:固定ブレークポイントの代わりにcqw+clampでフロイドレスポンシブを実現
- コンポーネントToken:コンテナクエリ+CSSカスタムプロパティでコンポーネントレベルテーマ
- コンテナクエリPolyfill:Container Query Polyfillで古いブラウザをサポート
- PostCSS変換:PostCSSプラグインでメディアクエリフォールバックを自動生成
- デザインシステム統合:コンポーネントライブラリでブレークポイントPropsの代わりにコンテナクエリを統一使用
比較分析
| 次元 | コンテナクエリ | メディアクエリ | Element Queries | JS Resize |
|---|---|---|---|---|
| クエリ対象 | 親コンテナ | ビューポート | 要素自身 | 要素自身 |
| CSSネイティブ | ✅ | ✅ | ❌ | ❌ |
| パフォーマンス | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
| コンポーネントレベル | ✅ | ❌ | ✅ | ✅ |
| ネスト対応 | ✅ | ✅ | ❌ | ✅ |
| ブラウザサポート | 主要 | すべて | Polyfill必要 | すべて |
まとめ:CSSコンテナクエリにより、レスポンシブデザインが「ページレベル」から「コンポーネントレベル」へ進化——コンポーネントはビューポート幅に依存せず、自身のコンテナ空間に適応。inline-sizeコンテナ+名前付きコンテナ+cqwユニットの三位一体で、コンポーネントライブラリはブレークポイントPropsを完全に排除。2026年、コンテナクエリはレスポンシブデザインの新標準となり、メディアクエリと役割分担:メディアクエリはページレイアウト、コンテナクエリはコンポーネント内部を管理。
オンラインツール推奨
- JSONフォーマッター:/ja/json/format
- Hash計算:/ja/encode/hash
- cURL→コード変換:/ja/dev/curl-to-code
ブラウザローカルツールを無料で試す →
#容器查询#响应式设计#CSS Container Query#组件级响应#2026#前端工程