CSS Gridで、カードを幅に合わせて自動で並べる方法

​​CSS​HTML,Playwright,レスポンシブ

カードを親の幅に合わせて自動で並べるには、CSS Grid で grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)) と書きます。メディアクエリを書かなくても、幅 220px 以上のカードが入るだけ横に並び、入りきらないカードは次の行に回ります。余った幅は、同じ行のカードに均等に配られます。

補足

検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。HTML と CSS だけのページを Playwright で開き、親の要素の幅を変えながら、カードの幅や1行の枚数を getBoundingClientRect() と getComputedStyle() で読みました。Firefox と Safari では試していません。各ブラウザーの対応バージョンは、互換性データ(@mdn/browser-compat-data 8.1.8)の値です。

親の要素の右端にあるつまみを左へドラッグすると、カードの列が4列、3列、2列、1列と減り、右へドラッグすると1列から4列に戻る。上の「列の数」の数字が変わった瞬間だけピンクになる
親の幅をドラッグで変えたときの動き。親の幅 960px から 240px まで狭め、また 960px に戻している(右のつまみ、マウスの絵、上の表示は GIF 用に足したもの)

基本は grid-template-columns の1行

html
<div class="cards">
  <div class="card">カード 1</div>
  <div class="card">カード 2</div>
  <!-- … -->
</div>
css
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 16px;
}

/* カードの見た目(後の flex-wrap の例のために border-box にしている) */
.card {
  box-sizing: border-box;
  padding: 16px;
  border: 2px solid #00c8e8;
  border-radius: 8px;
  background: #eefafd;
}

grid-template-columns は、グリッドの列の幅を決めるプロパティです。ここに書いた値は、次の2つを組み合わせています。

  • minmax(220px, 1fr):1列の幅を「220px 以上、残りの幅を分け合った分(1fr)以下」にする
  • repeat(auto-fill, …):その列を、親の幅からあふれない範囲で、できるだけ多く繰り返す

検証環境で、カード 6 枚の親の幅を変えて測ると、次のようになりました。

親の幅 列の数 カードの幅
455px 1 455px
456px 2 220px
691px 2 337.5px
692px 3 220px
927px 3 約 298.3px
928px 4 220px
960px 4 228px
1164px 5 220px

列が増えるのは、220px のカードと 16px の gap が、ちょうど入りきる幅になったときです。2列なら 220 × 2 + 16 = 456px、3列なら 220 × 3 + 16 × 2 = 692px で、表の境目と一致しました。列が増えた直後のカードは 220px ちょうどで、親が広がるにつれて余った幅を分け合って太くなり、次の列が入る幅になると、また 220px に戻ります。

列の数はこの最小の幅で決まるので、カードの中身が収まる幅を選びます。カードは列の幅いっぱいに広がり、padding と枠はその内側に収まります。カードの box-sizing を content-box(何も指定しないときの値)に戻しても、親の幅 960px でカードの幅は 228px のままでした。

同じ行のカードは、高さもそろいます。親の幅 960px で、1枚だけ文が長く4行に折り返されるカードを入れたところ、そのカードと同じ行の4枚がどれも高さ 132px になり、次の行の2枚は 60px のままでした。

カードが少ないときは、auto-fill と auto-fit で並び方が変わる

repeat() の1つ目には、auto-fill の代わりに auto-fit も書けます。MDN の repeat() の説明によると、auto-fit は auto-fill と同じように列を作り、カードを置いた後で、空の列をたたみます。

違いが出るのは、1行に入るカードの数より、カードの枚数が少ないときです。親の幅 960px にカードを 2 枚だけ置いて、列の幅を読みました。

書き方 列の幅 カードの幅
auto-fill 228px 228px 228px 228px 228px
auto-fit 472px 472px 0px 0px 472px

auto-fill では、カードが入っていない2列が残り、カードは 228px のまま左に寄りました。auto-fit では、空の2列が 0px にたたまれ、2枚のカードが 472px ずつに広がって、横幅いっぱいになりました。

上は auto-fill で、カード 1 とカード 2 の右に、点線の「空の列」が2つ残っている。下は auto-fit で、カード 1 とカード 2 が横幅いっぱいに広がっている
親の幅 960px にカードを 2 枚置いたとき。上が auto-fill、下が auto-fit(「空の列」の点線と見出しは、画像用に足したもの)

カードが 6 枚あって1行目が埋まるときは、どちらも列の幅が 228px × 4 で、違いはありませんでした。検索結果のように枚数が変わる一覧で、カードの幅をいつも同じにしておきたいなら auto-fill、少ないときは横に広げたいなら auto-fit を選びます。

狭い画面でカードがはみ出すときは、min() で最小幅を抑える

minmax(220px, 1fr) のままだと、親の幅が 220px より狭いときに、カードがはみ出します。親の幅を 200px にすると、列の幅は 220px のままで、カードの右端が親の外に 20px 出ました。MDN の repeat() の説明にも、どの回数でもあふれるときは、繰り返しの回数を 1 にすると書かれています。検証環境でも列は1つでしたが、その幅は 220px のままでした。

これを防ぐには、最小の幅を min() で「220px と、親の幅の小さいほう」にします。

css
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr));
  gap: 16px;
}

親の幅 200px で試すと、列の幅は 200px になり、カードは親の中に収まりました。親の幅 960px のときは、min() を付ける前と同じく 228px × 4 列で、広い画面での並び方は変わりませんでした。

上は minmax(220px, 1fr) で、点線で示した幅 200px の親から、カードの右側がはみ出している。下は minmax(min(220px, 100%), 1fr) で、カードが親の点線の中に収まっている
親の幅が 200px のとき。上は min() なし、下は min() あり(点線と説明の文字は、画像用に足したもの)

flex-wrap で並べると、最後の行のカードが伸びる

同じような並びは、Flexbox の flex-wrap: wrap でも作れます。

css
.cards-flex {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
}

.cards-flex > .card {
  flex: 1 1 220px;
}

親の幅 960px にカードを 5 枚置くと、どちらも1行目に4枚、2行目に1枚が並びました。違いは2行目です。flex-wrap では、最後の1枚が行の残りを全部使って 960px に伸びました。Grid の auto-fill では、2行目のカードも1行目と同じ 228px で、1行目のカード 1 の真下に並びました。

上は flex-wrap で、カード 1 から 4 が1行目に並び、2行目のカード 5 だけが横幅いっぱいに伸びている。下は grid の auto-fill で、カード 5 も上のカードと同じ幅で左の列にそろっている
親の幅 960px にカードを 5 枚置いたとき。上が flex-wrap、下が grid の auto-fill(見出しと点線は、画像用に足したもの)

flex-wrap の例では、カードの box-sizing: border-box が効いています。MDN の box-sizing の説明にあるとおり、content-box に戻すと、flex-basis の 220px は padding と枠を含まない幅になります。親の幅 960px で試すと、1行目に3枚(約 309.3px ずつ)、2行目に2枚が並び、4枚は入りませんでした。Grid の例では、上で書いたとおり幅は変わりませんでした。

カードの幅を行をまたいでそろえたい一覧には、Grid の auto-fill のほうが合います。

使えるブラウザーのバージョン

互換性データでは、grid-template-columns の repeat() は Chrome 57、Firefox 76、Safari 10.1 から使えます(Firefox は 52 から一部対応)。min() は Chrome 79、Firefox 75、Safari 11.1 からです。flex-wrap の例で使った Flexbox の gap は、Chrome 84、Firefox 63、Safari 14.1 からです。repeat() の対応状況は、MDN の repeat() のページにある互換性の表でも見られます。

​Posted by 86