コンテナクエリで親要素の幅に合わせてレイアウトを変える
コンテナクエリで親要素の幅に合わせてレイアウトを変える
カードコンポーネントを「サイドバーでは縦並び、メイン領域では横並び」みたいに、置き場所で見た目を変えたくなりました。メディアクエリで書こうとしたら条件がどんどん増えてつらくなったので、そのときに知ったコンテナクエリのメモです。
結論から書くと、CSSのコンテナクエリ(@container)を使えば、画面幅ではなく 親要素(コンテナ)の幅 に応じてスタイルを切り替えられます。コンポーネントが「自分が置かれた場所の広さ」を見てレイアウトを決められるので、置き場所ごとの分岐がぐっと減りました。
メディアクエリだと何が困るのか
メディアクエリ(@media)が見ているのは、いつもビューポート(画面)の幅です。
.card {
display: block;
}
@media (min-width: 600px) {
.card {
display: grid;
grid-template-columns: 120px 1fr;
}
}
これは「画面が600px以上なら横並び」という指定です。画面全体を基準にするぶんには分かりやすいです。
ですが、同じカードを 広いメイン領域 と 狭いサイドバー の両方に置きたいと、とたんに困ります。画面が広くても、サイドバーの中で横並びにすると窮屈になります。でもメディアクエリは画面幅しか見ていないので、「サイドバーの中のときだけ縦並び」を素直に書けません。
結局、親に .in-sidebar のようなクラスを足して置き場所ごとに上書きする……みたいなことをやって、条件が増えるほどしんどくなりました。
コンテナクエリは親の幅を見る
ここでコンテナクエリの出番です。やることは2つだけです。
まず、基準にしたい親要素に container-type を指定して「この要素をコンテナにする」と宣言します。
.card-wrapper {
container-type: inline-size;
}
inline-size は「横方向(インライン方向)のサイズを基準にする」という意味です。レスポンシブで見たいのはたいてい横幅なので、基本はこれでよさそうです。
次に、@container でそのコンテナの幅に応じたスタイルを書きます。
.card {
display: block;
}
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 120px 1fr;
}
}
ポイントは、@container の条件が 画面ではなく一番近いコンテナの幅 で評価されることです。つまり同じ .card でも、メイン領域に置けば横並び、サイドバーに置けば縦並びと、置き場所に合わせて勝手に切り替わります。カード側のCSSは1つのままでいいわけです。これは便利でした。
コンテナに名前を付ける
コンテナが入れ子になっていると、「どのコンテナを基準にしているの?」が分かりにくくなります。そういうときはコンテナに名前を付けられます。
.layout {
container-type: inline-size;
container-name: layout;
}
@container layout (min-width: 700px) {
.card {
grid-template-columns: 160px 1fr;
}
}
container-name で名前を付けて、@container layout (...) のように名前を指定すると、そのコンテナを基準に評価してくれます。container: layout / inline-size; とまとめて書くショートハンドもあります。
入れ子が浅いうちは名前なしでも動きますが、ネストしてくると名前があったほうが事故りにくいと思います。
cqi 単位で「コンテナ基準の大きさ」にする
ついでに知っておくと便利なのが、コンテナ基準の長さの単位です。cqw(コンテナ幅の1%)や cqi(インライン方向の1%)が使えます。
.card-title {
/* コンテナ幅に応じて文字サイズが変わる。下限・上限は clamp で挟む */
font-size: clamp(1rem, 4cqi, 1.5rem);
}
ビューポート基準の vw だと画面幅で大きさが決まってしまいますが、cqi ならコンテナの幅で決まります。カードが小さい場所では小さく、広い場所では大きく、を1行で表現できるのは気持ちいいです。
Tailwind CSSで使う場合
このサイトは Tailwind CSS v4 を使っているのですが、コンテナクエリはコア機能として入っています。コンテナにしたい要素に @container を付けて、子に @md: のような修飾子を使います。
<div class="@container">
<div class="block @md:grid @md:grid-cols-[120px_1fr]">
<!-- カードの中身 -->
</div>
</div>
素のCSSの container-type: inline-size が @container クラス、@container (min-width: ...) が @md: のような修飾子に対応している、というイメージです。v3 まではプラグインが必要だったみたいですが、v4 からは標準で使えるのは助かります。
注意点
いくつか踏んだところと、未確認のところを書いておきます。
container-type: inline-sizeを指定した要素は、インライン方向のサイズが中身から独立 します。コンテンツに応じて勝手に広がる挙動が変わることがあるので、レイアウトが崩れたらここを疑うとよさそうです。- コンテナ自身には
@containerのスタイルを当てられません。あくまで コンテナの中の子要素 に効きます。最初これで「なんで効かないの」と少し悩みました。 - 対応ブラウザは、いまどきのモダンブラウザならだいたい大丈夫なようです。ただし古い環境も相手にする場合は、メディアクエリでのフォールバックを併用するか
@supportsで出し分けるのが安全だと思います(このあたりは案件によりけりなので、対象ブラウザは都度確認したほうがよさそうです)。
まとめ
- メディアクエリは画面幅基準なので、「置き場所で見た目を変えるコンポーネント」とは相性が悪い
- コンテナクエリは 親(コンテナ)の幅基準 なので、コンポーネント単位でレスポンシブを書ける
- 使い方は「親に
container-type: inline-size」+「@container (min-width: ...)でスタイル」の2ステップ cqi単位や Tailwind の@containerも合わせると書きやすい
メディアクエリを完全に置き換えるものではなく、「ページ全体のレイアウトはメディアクエリ、再利用するコンポーネントの中はコンテナクエリ」くらいの使い分けが今のところしっくりきています。Flexbox と Grid の使い分けはFlexboxとGridの記事にも書いたので、合わせて読むと組み立てやすいと思います。詳しい仕様はMDNのコンテナクエリを見つつ、まずは1つのカードで試してみるとよさそうです。以上です。