はじめに
CSS Grid Layoutには、ネストしたグリッドの子要素を親グリッドのトラックに参加させるsubgridという機能があります。カードUIのようなコンポーネントで、タイトルや本文、ボタンの位置を複数カード間で揃えたいとき、従来はflexboxの工夫や固定高さで対処していた処理を、シンプルなCSSで解決できます。
本記事は、subgridの仕組みと使い方を、基本の構成から画像つきカードの応用例まで紹介します。
subgridはChrome 117+、Safari 16+、Firefox 71+の各メジャーブラウザで使用可能です。
subgridとはどんな機能か
通常のグリッドレイアウトでは、ネストしたグリッドは外側の親グリッドとは独立したトラックサイズを持ちます。複数の子コンポーネントをまたいで高さや幅を揃えることができない、というのが長年の課題でした。
subgridは、この制約を解消するための値です。grid-template-rows: subgridまたはgrid-template-columns: subgridと指定すると、そのグリッドアイテムは独自のトラックを作らず、親グリッドのトラック定義を引き継ぎます。
/* 親グリッドの行トラックを引き継ぐ */
.card {
display: grid;
grid-row: span 3; /* 親グリッドの行3つ分を使う */
grid-template-rows: subgrid;
}
この仕組みにより、横に並ぶ複数のカードがそれぞれ親の同じ行トラックを参照するようになります。「タイトルが1行のカード」と「タイトルが2行のカード」が混在していても、各部位が自動的に同じ高さに揃います。
flexboxとの比較
subgridが登場する前、複数カードのボタン下揃えには主にflexboxが使われていました。
/* flexboxでのボタン下揃え */
.card {
display: flex;
flex-direction: column;
}
.card__body {
flex: 1; /* 本文を伸ばしてボタンを下端に押し下げる */
}
この方法でボタンの下揃えは実現できますが、カードをまたいで各部位の高さを一致させることはできません。タイトルが1行のカードと2行のカードが並ぶと、本文の開始位置がカードごとにずれます。subgridを使うと、この「部位ごとの高さ揃え」を自動で行えます。
subgridで高さを揃える基本の構成
3列のカードグリッドを例にします。各カードはタイトル・本文・リンクの3要素で構成されています。
See the Pen CSS subgrid v1 by watanabe (@web-sourcecode) on CodePen.
HTML
<div class="card-grid">
<article class="card">
<h2 class="card__title">短いタイトル</h2>
<p class="card__body">本文が短いカードです。</p>
<a class="card__link" href="#">詳しく見る</a>
</article>
<article class="card">
<h2 class="card__title">これはかなり長いタイトルで2行になるケース</h2>
<p class="card__body">本文は少し長めです。複数行になることもあります。</p>
<a class="card__link" href="#">詳しく見る</a>
</article>
<article class="card">
<h2 class="card__title">普通の長さのタイトル</h2>
<p class="card__body">本文テキスト。</p>
<a class="card__link" href="#">詳しく見る</a>
</article>
</div>CSS
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.card {
display: grid;
/* 親グリッドの行3つ分を占有する */
grid-row: span 3;
/* 親グリッドの行トラックを引き継ぐ */
grid-template-rows: subgrid;
padding: 16px;
border: 1px solid #ddd;
border-radius: 8px;
}
.card__title {
font-size: 1.1rem;
margin: 0 0 8px;
}
.card__body {
margin: 0;
color: #555;
}
.card__link {
align-self: end; /* リンクをセル内の下端に揃える */
display: inline-block;
padding: 8px 16px;
background: #0066cc;
color: #fff;
border-radius: 4px;
text-decoration: none;
}
grid-row: span 3は「このカードは親グリッドの行を3つ使う」という指定です。カード内の3つの子要素がそれぞれ1行ずつ担当し、同じ行グループに並ぶすべてのカードでタイトル行・本文行・リンク行の高さが一致します。
spanの数はカードの直接の子要素数と合わせてください。子要素が4つならgrid-row: span 4です。
画像つきカードへの応用
画像つきのカードでも同じ考え方が使えます。子要素が増えた分、spanの数を増やします。
See the Pen CSS subgrid v2 by watanabe (@web-sourcecode) on CodePen.
HTML
<div class="card-grid">
<article class="card">
<img class="card__img" src="image1.jpg" alt="カード画像1">
<h2 class="card__title">短いタイトル</h2>
<p class="card__body">本文テキスト。</p>
<a class="card__link" href="#">詳しく見る</a>
</article>
<article class="card">
<img class="card__img" src="image2.jpg" alt="カード画像2">
<h2 class="card__title">これはかなり長いタイトルで2行になるケース</h2>
<p class="card__body">本文は少し長め。複数行になることもあります。</p>
<a class="card__link" href="#">詳しく見る</a>
</article>
<!-- 他のカード... -->
</div>CSS
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.card {
display: grid;
grid-row: span 4; /* 画像・タイトル・本文・リンクの4行 */
grid-template-rows: subgrid;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
}
.card__img {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
.card__title,
.card__body {
padding: 0 16px;
}
.card__title {
font-size: 1.1rem;
margin: 16px 0 8px;
}
.card__body {
margin: 0;
color: #555;
}
.card__link {
align-self: end;
display: inline-block;
margin: 0 16px 16px;
padding: 8px 16px;
background: #0066cc;
color: #fff;
border-radius: 4px;
text-decoration: none;
}
画像エリアはaspect-ratioで比率を固定しているため、元画像の縦横比が異なっていてもカード間で高さが揃います。
列方向への応用
subgridは列方向(grid-template-columns: subgrid)にも使えます。複数の行を持つフォームで、ラベルと入力フィールドを親グリッドの列に揃えるようなケースで有効です。
See the Pen CSS subgrid v3 by watanabe (@web-sourcecode) on CodePen.
HTML
<form class="form-grid">
<div class="form-row">
<label for="name">氏名</label>
<input id="name" type="text">
</div>
<div class="form-row">
<label for="email">メールアドレス</label>
<input id="email" type="email">
</div>
</form>
CSS
.form-grid {
display: grid;
grid-template-columns: 160px 1fr; /* ラベル列・入力列 */
gap: 12px 16px;
}
.form-row {
display: grid;
/* 親グリッドの全列を使う */
grid-column: 1 / -1;
/* 親グリッドの列トラックを引き継ぐ */
grid-template-columns: subgrid;
}
各.form-rowが親の160px 1frという列定義を引き継ぐため、ラベルと入力フィールドが全行で縦に揃います。
注意点
spanの数と子要素数を合わせる
spanの数と実際の直接の子要素数がずれると、レイアウトが崩れます。子要素が3つなのにspan 4にすると空のトラックができてしまいます。子要素の数が動的に変わるコンポーネントでは、spanの値をJavaScriptで調整するか、子要素数を固定して設計するのが安定します。
ブラウザ対応とフォールバック
Chromeは117(2023年9月)、Safariは16(2022年9月)からの対応です。@supportsでフォールバックを用意しておくと、対応していない環境でもレイアウトが崩れにくくなります。
/* フォールバック:flexboxでボタンだけ下揃えにする */
.card {
display: flex;
flex-direction: column;
}
.card__body {
flex: 1;
}
/* subgrid対応ブラウザ向け */
@supports (grid-template-rows: subgrid) {
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.card {
display: grid;
grid-row: span 3;
grid-template-rows: subgrid;
flex-direction: unset;
}
.card__body {
flex: unset;
}
}
フォールバック環境ではボタンの下揃えまで、subgrid対応環境では部位ごとの高さ揃えまで提供できます。
孫要素は揃えられない
subgridが揃えられるのは、直接の子要素だけです。カード内にさらにネストした要素がある場合は、もう一段subgridを重ねる必要があります。仕様上は多重ネストも可能ですが、構造が複雑になるため、揃えたい要素をなるべく直接の子要素に配置する設計が管理しやすいです。
まとめ
CSS subgridを使うと、ネストしたグリッドの子要素を親グリッドのトラックに参加させられます。カードUIでタイトル・本文・ボタンを複数カード間で揃えるには、grid-row: span Nで占有行数を指定し、grid-template-rows: subgridで親のトラックを引き継ぐのが基本の形です。
古いブラウザへの配慮が必要な場合は@supportsでflexboxのフォールバックを組み合わせておくと、幅広い環境でレイアウトを維持できます。
ポイント
grid-template-rows: subgridで、親グリッドの行トラックをネストしたアイテムに引き継ぐgrid-row: span Nでカードが使う行数を指定する。Nはカードの直接の子要素数と合わせる- 列方向には
grid-template-columns: subgridが使える @supports (grid-template-rows: subgrid)でフォールバックを書けるsubgridが揃えられるのは直接の子要素のみ