1. ホーム
  2. CSS

【CSS】subgrid の使い方|親グリッドの行・列を引き継いでカードの高さをそろえる方法

Share

カードを横に3枚並べたとき、タイトルの長さがバラバラでボタンの位置がずれてしまう——CSS Grid でレイアウトを組んでいると必ず出会う悩みです。これは、カードの中身が親グリッドとは別の独立したグリッドになっているのが原因です。subgrid を使うと、子要素が親グリッドの行や列をそのまま引き継げるので、入れ子になった要素どうしをきれいにそろえられます。この記事では subgrid の考え方と書き方、カードやフォームでの実例、そして効かないときの原因までを解説します。

subgrid は親グリッドのトラックを子に引き継ぐ仕組み

グリッドレイアウトでは、display: grid を指定した要素の直接の子だけがグリッドアイテムになります。孫要素は関係ありません。そのため、カードの中に <h3> や <p> を入れてカード自身もグリッドにすると、そこには親とはまったく別の行・列が新しく作られます。カードごとに中身の量が違えば、当然それぞれの行の高さもバラバラになります。

subgrid は、この「別のグリッド」を作らずに親のトラック(行や列の区画)をそのまま借りてくるためのキーワードです。grid-template-columns か grid-template-rows の値として書くと、その軸のトラックは自分では決めず、親グリッドのものを使います。結果として、孫要素までが親と同じ格子の上に乗ることになります。

style.css
.parent {
  display: grid;
  grid-template-columns: 80px 1fr 120px; /* 親が持つ3本の列 */
}

.child {
  grid-column: span 3;            /* 親の3列ぶんを占有する */
  display: grid;                  /* 自分もグリッドコンテナにする */
  grid-template-columns: subgrid; /* 列は親のものを引き継ぐ */
}

ここで大事なのは、引き継がれるのがその要素が占めている範囲のトラックだけという点です。上の例で grid-column: span 3 を書かずに1列ぶんしか占有していなければ、引き継げる列も1本だけになり、思ったようにそろいません。subgrid と span はセットで覚えてください。

実際に動かしてみましょう。.child の中の A・B・C が、下にある通常のアイテムではなく親の列幅(80px / 可変 / 120px)に沿って並んでいるのが分かります。grid-template-columns: subgrid の行を消すと、3つが均等幅になって親とのそろいが崩れます。

<div class="parent">
  <div class="child">
    <span>A</span>
    <span>B</span>
    <span>C</span>
  </div>
  <div class="box">通常のアイテム</div>
</div>
.parent {
  display: grid;
  /* 親は幅の違う3本の列を持つ */
  grid-template-columns: 80px 1fr 120px;
  gap: 8px;
  font-family: sans-serif;
  font-size: 13px;
}

.child {
  /* 親の3列ぶんを占有する */
  grid-column: span 3;
  /* 自分もグリッドにして、列トラックを親から引き継ぐ */
  display: grid;
  grid-template-columns: subgrid;

  margin-bottom: 8px;
}

.child span,
.box {
  padding: 8px;
  border: 1px solid #90caf9;
  background: #e3f2fd;
  text-align: center;
}

.box {
  grid-column: span 3;
  border-color: #ddd;
  background: #f5f5f5;
}
Preview

カードの見出し・本文・ボタンの位置をそろえる

subgrid がいちばん活躍するのが、冒頭に挙げたカードの高さそろえです。カード1枚を「見出し・本文・ボタン」の3行構成と考え、親グリッド側にその3行を用意します。各カードは grid-row: span 3 で3行ぶんを占有し、grid-template-rows: subgrid で行トラックを引き継ぎます。

こうすると、見出しの行の高さはすべてのカードの見出しの中でいちばん高いものに合わせて決まります。本文の行も同様です。カードごとに文章量が違っても、ボタンの位置は自然にそろいます。従来は flex と margin-top: auto でボタンだけを下に押しやる書き方が定番でしたが、それだと「本文の開始位置」まではそろいませんでした。

<div class="cards">
  <article class="card">
    <h3>基本プラン</h3>
    <p>まずは試してみたい方に。</p>
    <a class="btn" href="#">詳しく見る</a>
  </article>
  <article class="card">
    <h3>チームで使える標準プラン</h3>
    <p>メンバーを追加して共同編集できます。サポートの返信も優先されます。</p>
    <a class="btn" href="#">詳しく見る</a>
  </article>
  <article class="card">
    <h3>大規模プラン</h3>
    <p>大人数の運用向け。</p>
    <a class="btn" href="#">詳しく見る</a>
  </article>
</div>
.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  font-family: sans-serif;
}

.card {
  /* 「見出し・本文・ボタン」の3行ぶんを占有する */
  grid-row: span 3;
  /* 行トラックを親から引き継ぐ(ここが subgrid の肝) */
  display: grid;
  grid-template-rows: subgrid;

  padding: 12px;
  border: 1px solid #ddd;
  border-radius: 6px;
}

.card h3 {
  margin: 0;
  font-size: 15px;
}

.card p {
  margin: 0;
  font-size: 13px;
  color: #555;
  line-height: 1.6;
}

.btn {
  /* 行の下端にそろえる */
  align-self: end;
  padding: 6px 10px;
  border-radius: 4px;
  background: #007bff;
  color: #fff;
  font-size: 13px;
  text-align: center;
  text-decoration: none;
}
Preview

親側に grid-template-rows を書いていない点に気づいたでしょうか。grid-row: span 3 によって行は暗黙的に3本作られ、その高さは中身に応じて自動で決まります。行数をはっきり示したい場合は、親に grid-template-rows: repeat(3, auto); と書いても同じ結果になります。

なお .btn に付けた align-self: end は、ボタンが行の高さいっぱいに引き伸ばされるのを防ぎ、下端にそろえるための指定です。subgrid にした軸でも、こうした配置系のプロパティは通常どおり効きます。

フォームのラベル幅を行をまたいでそろえる

列方向の subgrid は、フォームで力を発揮します。「ラベル+入力欄」を <div> でひとまとまりにすると、マークアップとしては分かりやすい反面、各行が独立してしまうためラベルの幅が行ごとにズレます。かといって <div> を外して <label> と <input> を直接並べると、行単位の余白調整や条件分岐がしにくくなります。

subgrid なら、<div> の構造を保ったままラベル列の幅を共有できます。親で grid-template-columns: auto 1fr と定義し、各行の <div> にその2列を引き継がせるだけです。「電話番号(任意)」のような長いラベルに合わせて、すべての入力欄の左端がそろいます。

<form class="form">
  <div class="row">
    <label for="name">お名前</label>
    <input id="name" type="text">
  </div>
  <div class="row">
    <label for="mail">メールアドレス</label>
    <input id="mail" type="email">
  </div>
  <div class="row">
    <label for="tel">電話番号(任意)</label>
    <input id="tel" type="tel">
  </div>
</form>
.form {
  display: grid;
  /* ラベル列は中身に合わせ、入力欄列は残り幅すべて */
  grid-template-columns: auto 1fr;
  gap: 10px 12px;
  font-family: sans-serif;
  font-size: 14px;
}

.row {
  /* 1行ぶんの div が親の2列を占有する */
  grid-column: span 2;
  /* 列トラックを親から引き継ぐので、行をまたいでラベル幅がそろう */
  display: grid;
  grid-template-columns: subgrid;
  align-items: center;
}

.form input {
  padding: 6px 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 14px;
}
Preview

subgrid にした軸で書けること・書けないこと

subgrid を指定した軸は「トラックの本数とサイズを親に任せる」状態になります。そのためサイズに関する指定は無効になりますが、余白や配置は自分で決められます。混同しやすいところを整理しておきます。

指定subgrid にした軸での扱い
1fr / minmax() などのサイズ書けない。トラックのサイズは親が決める
grid-auto-rows / grid-auto-columns無視される。引き継いだ範囲より外にトラックは作られない
gap / row-gap / column-gap既定では親の値を引き継ぐ。自分で指定すれば上書きできる
名前付きライン親のライン名をそのまま使える。subgrid [名前] の形で名前を追加もできる
align-items / justify-items / align-self通常どおり効く
padding / border効くが、その分だけ中身が親のライン位置から内側にずれる

引き継いだ範囲からはみ出す位置にアイテムを置こうとした場合、そのアイテムは範囲の端に収められます。たとえば3行ぶんを引き継いだサブグリッドの中で grid-row: 5 を指定しても、5行目が新しく作られることはありません。

もうひとつ注意したいのが表の最後にある padding です。カードに padding: 12px を付けると、その内側でトラックが配置されるため、厳密には親のグリッド線と中身の位置が 12px ずれます。ただしカードどうしの見え方はそろうので、実務上は問題になりません。親のライン位置にぴったり合わせたいときは、パディングをカードではなく中の各要素に持たせてください。

subgrid が効かないときに確認すること

サブグリッドにする要素自身が display: grid になっていない

grid-template-columns: subgrid は、あくまでグリッドコンテナのプロパティです。display: grid(または inline-grid)を書き忘れていると、値そのものが適用されません。subgrid を書く要素には必ず display: grid をセットで書く、と覚えておくと確実です。

1トラックしか占有していない

もっとも多いのがこの見落としです。span を書かないとサブグリッドは1トラックぶんしか占有しないため、引き継ぐトラックも1本だけになり、見た目は subgrid なしとほとんど変わりません。「そろえたい要素の数ぶんの span」を必ず指定してください。カード内が3要素なら grid-row: span 3 です。

親がグリッドコンテナになっていない

subgrid は「親グリッドのトラックを引き継ぐ」機能なので、引き継ぐ相手がいなければ成立しません。親が Flexbox(display: flex)だったり、単なるブロック要素だったりする場合、subgrid は none と同じ扱いになります。この場合、子は普通のグリッドコンテナとして振る舞い、トラックは中身に応じて自動生成されます。

古いブラウザーへの対応

subgrid は Firefox 71(2019年)、Safari 16(2022年)、Chrome / Edge 117(2023年)で対応し、現在の主要ブラウザーではひととおり使えます。それより古い環境も考慮するなら、@supports で分岐させるのが安全です。対応していないブラウザーでは、カードの中を Flexbox で組んでボタンだけ下端に寄せる、といった従来の書き方にフォールバックさせられます。

style.css
/* 未対応ブラウザー向けの書き方をベースにする */
.card {
  display: flex;
  flex-direction: column;
}

.card .btn {
  margin-top: auto; /* ボタンだけ下端へ */
}

/* 対応していれば subgrid で上書きする */
@supports (grid-template-rows: subgrid) {
  .card {
    display: grid;
    grid-row: span 3;
    grid-template-rows: subgrid;
  }

  .card .btn {
    margin-top: 0;
    align-self: end;
  }
}

まとめ

subgrid は、入れ子になった要素を親グリッドと同じ格子の上に乗せるためのキーワードです。grid-template-columns / grid-template-rows に指定すると、その軸のトラックを親から引き継ぎます。

使うときの条件は3つです。サブグリッドにする要素が親グリッドのアイテムであること、その要素自身が display: grid であること、そして span でそろえたい数のトラックを占有していること。この3つがそろえば、カードのボタン位置もフォームのラベル幅も、余計な JavaScript や固定値なしできれいにそろいます。

参考ページ