CSS講座 / コンテナークエリー:再利用できるカードを作る
コンテナーサイズクエリーは、画面全体ではなく条件を評価できる祖先コンテナーの大きさに応じて子孫のCSSを変えます。container-typeで基準を用意し、@containerで部品内の配置を調整します。
画面の条件と部品の条件を分ける
幅1200pxの画面でも、カードが700pxの本文にあるか300pxのサイドバーにあるかで適切な内部配置は違います。どちらも同じビューポートを使うメディアクエリーだけでは、この違いを直接表せません。
@mediaはページ全体の列数やサイドバー位置、@containerは与えられた幅での画像と本文の並びなどに使い分けられます。コンテナーも「横長画面なら横並び」ではなく実際のサイズ条件です。サイドバーが広がって条件を満たせば、そのカードも横並びになります。常に狭くしたければ外側の最大幅も必要です。
サイズを調べる親を宣言する
.slot { container: panel / inline-size; }
@container panel (min-width: 30rem) {
.card { display: grid; grid-template-columns: 8rem minmax(0, 1fr); gap: 1rem; }
}
container: panel / inline-sizeは名前panelとinline-sizeのサイズ封じ込めを宣言します。横書きではinline方向は横ですが、縦書きでは対応が変わります。外側のブロック配置やGridの列から、コンテナーへ調べる幅を与えます。
条件を調べるのは.slot、条件付きスタイルを受けるのは子孫の.cardです。カード自身のサイズを調べて同じカード自身を変更する構造にはしません。サイズ変更と条件が循環するのを避けるため、適切な祖先が基準になります。
inline-sizeの封じ込めは、子孫の内容がその軸のサイズへ寄与することを制限します。内容に合わせて縮む配置では予想より小さくなることもあります。sizeはもう一方の軸にも作用し、内容で決まる高さが潰れる場合があるため、安易に置き換えないでください。MDNのcontainer-typeを参照してください。
異なる親幅へ同じカードを置く
次をcontainer-card.htmlとして保存します。色の箱が画像領域の代わりなので外部画像は不要です。広い画面では本文と最大20remのサイドバー、44rem以下では一列にします。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>container-card.html</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font: 1rem/1.6 sans-serif; color: #243447; }
h1, h2, p { margin-top: 0; } a { color: #145f8f; }
a:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid #ac4100; outline-offset: 3px; }
.layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 20rem); gap: 1rem; width: min(100%, 75rem); margin-inline: auto; padding: 1rem; }
.slot { min-width: 0; container: panel / inline-size; }
.card { min-width: 0; padding: 1rem; background: #e8f3fa; overflow-wrap: anywhere; }
.media { min-height: 7rem; background: #287699; }
.card h2 { font-size: 1.25rem; } .card p { margin-bottom: 0; }
@container panel (min-width: 30rem) {
.card { display: grid; grid-template-columns: 8rem minmax(0, 1fr); gap: 1rem; align-items: start; }
}
@media (max-width: 44rem) { .layout { grid-template-columns: minmax(0, 1fr); } }
</style>
</head>
<body>
<div class="layout">
<main class="slot"><article class="card"><div class="media" aria-hidden="true"></div><div><h2>Card in the main content area</h2><p>The image and text appear side by side when the parent is at least 30rem wide.</p></div></article></main>
<aside class="slot"><article class="card"><div class="media" aria-hidden="true"></div><div><h2>The same card in a sidebar</h2><p>The sidebar in this example stays at or below 20rem even on a wide screen, so the card keeps its vertical layout.</p></div></article></aside>
</div>
</body>
</html>
初期フォント16pxなら30remは480pxです。本文側の親が480px以上ならカード内が二列、最大320pxのサイドバー内なら縦並びになります。600px程度の画面でサイドバーが下へ移り、親が十分広くなれば両カードが横並びになっても正常です。
外側の44remはページ領域の配置、内側の30remはカード内の配置を決めています。同じpanelという名前でも、各カードは自分の祖先から基準を選びます。メディアクエリーとGridも参照してください。
最も近い祖先と名前付き祖先を比較する
名前のないサイズクエリーは、その条件を評価できる最も近い祖先コンテナーを選びます。次をcontainer-nested.htmlとして保存します。外側は広くても、内側は最大17remです。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>container-nested.html</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font: 1rem/1.6 sans-serif; color: #243447; }
h1, h2, p { margin-top: 0; } a { color: #145f8f; }
a:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid #ac4100; outline-offset: 3px; }
main { width: min(100%, 42rem); padding: 1rem; }
.outer { width: 100%; container: outer / inline-size; border: 2px solid #789; padding: 1rem; }
.inner { width: min(100%, 17rem); container-type: inline-size; }
.card { padding: 1rem; background: #e8f3fa; overflow-wrap: anywhere; }
.media { min-height: 5rem; background: #287699; }
@container (min-width: 30rem) {
.card { display: grid; grid-template-columns: 5rem minmax(0, 1fr); gap: 1rem; }
}
</style>
</head>
<body>
<main><h1>Which parent's width is queried?</h1><div class="outer"><div class="inner"><article class="card"><div class="media" aria-hidden="true"></div><p>The nearest inner container has a maximum of 17rem.</p></article></div></div></main>
</body>
</html>
名前のない条件は狭い内側を選ぶため、カードは縦のままです。ほかを変えず、@containerの条件だけを次へ置き換えます。
@container outer (min-width: 30rem) {
.card { display: grid; grid-template-columns: 5rem minmax(0, 1fr); gap: 1rem; }
}
outerという祖先を選ぶと、広い画面では条件が一致して横並びになります。ただしカード自体は17rem以内なので、本文が読める幅かは別に確認します。名前を付ければ不適切な基準が自動修正されるわけではありません。比較後は名前のない条件へ戻します。
条件が適用されないときに確認する
- 対象がコンテナーの子孫であり、親自身に条件を適用しようとしていないか確認します。
- container-typeと実際の内容幅を開発者ツールで見ます。paddingやborderを含む外幅と混同しないでください。
- 間に別のコンテナーができていないかを調べ、必要なら名前で基準を指定します。
- typeだけをnormalへ変えて効かなくなる差を確認し、inline-sizeへ戻します。
まず縦並びでも読める基本形を作り、条件に対応する環境で横へ広げます。未対応の環境でも基本形を使える構造にして、必要なブラウザーで対応と動作を確認します。MDNのコンテナーサイズ・スタイルクエリーを参照してください。




