TT Lab
はじめる
学ぶ 学習パス コース

CSS — 後に書いた規則が勝つ

カードは自分がどこに置かれたか知らない

TT Labで続きを見る

一言でいうと

flexは1つの軸を分け、gridは2つの軸を同時に決めます。そして、レスポンシブの本当の問いは「画面が何pxか」ではなく、このボックスが何pxかです。その問いに答えるのが、コンテナークエリです。

なぜ必要なのか

カードのコンポーネントを1つ作ります。狭いときは縦に、広いときは横に並ぶようにしたくて、メディアクエリを使います。

.card { display: block; }
@media (min-width: 700px) { .card { display: flex; } }

このカードを本文に置くと、うまく動作します。ところが、同じカードを狭いサイドバーに置くと、壊れます。画面は1400pxなのでメディアクエリは真ですが、カードが収まっているボックスは280pxだからです。

コンポーネントは、自分がどこに置かれたかを知りません。メディアクエリは画面の幅しか問いませんが、コンポーネントが知る必要があるのは、自分のボックスの幅です。このずれが、デザインシステムで最もよく起きる構造的な問題でした。

どう動くのか: flexとgrid

どちらを使うかは、好みではなく次元で分かれます。MDNの比較ドキュメントが、この関係を整理しています。

状況 適したもの
1行(または1列)の中で分ける flex
内容の大きさに応じて自動で配分する flex
行と列を両方決める grid
画面の骨格: ヘッダー・サイド・本文・フッター grid
個数が変わっても、位置を守る必要がある grid

flexは内容から出発します。アイテムが自分の大きさを決め、余った分や足りない分を分けます。gridは枠から出発します。先に列を決め、アイテムをその列に入れます。

そのため、ツールバーやボタンの行のように「中に何が入るかわからない1行」にはflexが合い、ページの骨格のように「列が先に決まっているもの」にはgridが合います。

flexアイテムが縮まない問題

flexで最もよく出会う落とし穴があります。長い文章や幅の広い表が入ったアイテムが、縮まずにコンテナーを押し広げることです。

原因は、flexアイテムのmin-widthの初期値がautoだということです。この値は「内容の最小サイズより小さくならない」という意味なので、折り返せない長い文字列があれば、その分は必ず占めます。MDNのflex比率のドキュメントが、この動作を説明しています。

.item { min-width: 0; }        /* 또는 overflow: hidden */

この1行が、「なぜ横スクロールが出るのか」の答えであることが、非常に多くあります。

flex: 1は、flex-grow: 1; flex-shrink: 1; flex-basis: 0%の省略形です。flex-basis: 0%なので、内容の大きさを無視して、余った領域を等しく分けます。flex: autoはflex-basis: autoなので、内容の大きさから出発します。この2つを混ぜて使うと、予想と違う結果になります。

gridで、列の数を数えずに配置する

gridには、メディアクエリなしで反応する定型表現があります。

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
}

「最小240pxの列を、入るだけ作り、余った場所は等しく分けなさい」という意味です。画面の幅を数えなくても、列の数が自動で変わります。CSS Grid 2が、auto-fitとauto-fillの違いを定義しています。auto-fillは空の列を残し、auto-fitは空の列を折りたたんで、残ったアイテムが広がります。

コンテナークエリ

それでも残る問題があります。上の定型表現は列の数は変えますが、カードの中の配置は変えられません。カードが240pxのときと600pxのときで内側の構造が違う必要があるなら、カードが自分の幅を問う必要があります。

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

@container (min-width: 420px) {
  .card { display: grid; grid-template-columns: 120px 1fr; }
}

container-type: inline-sizeは、「この要素のインライン方向のサイズを、クエリの対象にします」という宣言です。すると、その子孫が@containerで、祖先の幅を問えるようになります。CSS Containment 3が定義し、MDNのコンテナークエリのドキュメントが使い方を整理しています。

2つのことを覚えておきます。

現場での姿

実際に経験したことが3つあります。

1つ目は、同じコンポーネントが2か所で異なる壊れ方をすることです。本文では問題なく、サイドバーでだけ壊れます。これは、ほとんどいつも、メディアクエリを使うべき場所に、コンテナークエリが必要だったケースです。

2つ目は、横スクロールが出たのに犯人が見つからないことです。flexアイテムのmin-width: autoが原因であることが最も多く、次がwidth: 100vw(スクロールバーの幅が含まれます)です。

3つ目は、メディアクエリのブレークポイントが10個を超えることです。これはたいてい、コンポーネントが画面の幅を問う理由がないのに、問い合わせているというサインです。repeat(auto-fit, minmax(...))とコンテナークエリに移すと、ブレークポイントの大部分がなくなります。

次のクイズで確認すること

flexとgridの使い分け、flex: 1とflex: autoの違い、min-width: autoが生むはみ出し、auto-fitとauto-fillの違い、そして、コンテナークエリがメディアクエリと何が違うのかを確認します。