Web制作をしていると、カード型UIのように同じ要素を並べたときに、中身のテキスト量の違いでレイアウトがガタガタになってしまう、なんて経験はありませんか?
この記事では、そんな悩みを解決するCSSのSubgrid
という機能について、基礎から実践まで分かりやすく解説します。
Subgrid
をマスターすれば、これまでJavaScriptに頼っていたり、諦めたりしていた複雑なレイアウト調整が、CSSだけで驚くほど簡単に実現できます。ぜひ、この機会にSubgrid
をあなたの武器に加えましょう。
CSS Subgridとは?Gridレイアウトの「入れ子問題」を解決する新機能
Webサイト制作で多用されるCSS Grid Layoutは、二次元のレイアウトを簡単に組める非常に強力な機能です。しかし、Gridの中にさらにGridを入れ子(ネスト)にした場合、ある悩ましい問題が発生します。それは、**「親のGridと子のGridのラインが揃わない」**という問題です。
例えば、商品が並んだカード一覧を想像してみてください。各カード自体をGridで作り、そのカードをさらにGridで並べると、カード内のタイトルや画像、説明文の高さが他のカードとピタッと揃わず、ちぐはぐな印象になってしまいます。それぞれのカードが独立したGridコンテナとして振る舞うため、他のカードのコンテンツの高さなど知る由もなかったのです。
この「入れ子にしたGridの連携が取れない問題」をエレガントに解決するために登場したのが、CSS Subgridです。Subgridを使うと、入れ子になった内側のGrid(子グリッド)が、外側のGrid(親グリッド)の行と列の定義を「継承」できるようになります。まるで、親が引いた設計図の線を、子どもがそのまま利用するようなイメージです。これにより、これまで実現が難しかった、入れ子構造全体のアイテムをピクセルパーフェクトに揃える、美しく整合性のとれたレイアウトが可能になるのです。
CSS Subgridの基本的な仕組みと使い方
Subgridの概念は一見すると少し難しく感じるかもしれませんが、基本的な仕組みはとてもシンプルです。ここでは、Subgridが登場する前の課題と、Subgridがどのようにその課題を解決するのかを、具体的なコードを交えながら見ていきましょう。
Subgridが登場する前の課題:ネストしたGridの限界
まずは、Subgridがない世界でどのような問題が起きていたかを確認します。以下は、Gridレイアウトの中に、さらにカード型のGridを配置した典型的な例です。
<div class="grid-container">
<div class="card">
<div class="card-header">タイトルA</div>
<div class="card-body">ここの文章は短いです。</div>
<div class="card-footer">フッター</div>
</div>
<div class="card">
<div class="card-header">タイトルB</div>
<div class="card-body">こちらのカードの本文は、他のカードよりも少し長めのテキストが入っています。レイアウトがどうなるか見てみましょう。</div>
<div class="card-footer">フッター</div>
</div>
</div>
```css
.grid-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.card {
display: grid;
grid-template-rows: auto 1fr auto; /* ヘッダー、ボディ、フッターの3行 */
border: 1px solid #ccc;
border-radius: 8px;
}
/* 以下、カード内のスタイル */
.card-header, .card-body, .card-footer {
padding: 15px;
}
.card-header { background-color: #f0f0f0; }
.card-footer { background-color: #f0f0f0; }
このコードの表示結果を見ると、カードBの本文(.card-body
)が長いため、カード全体の高さは揃いますが、カードAのフッター(.card-footer
)がカードBのフッターと同じ位置に来ず、上にずれてしまっているのが分かります。各カードが独立したGridであるため、内部の行(row)の位置を互いに合わせることができないのです。
Subgridの魔法:親グリッドの定義を継承する
この問題を解決するのがsubgrid
です。使い方は非常に簡単で、子要素となるグリッドアイテム(今回は.card
)のgrid-template-rows
やgrid-template-columns
プロパティの値をsubgrid
に変更するだけです。
まず、親グリッド(.grid-container
)の行(row)を定義します。カードのヘッダー、ボディ、フッターに対応する3つの行を定義するのがポイントです。
/* 親グリッドのCSSを修正 */
.grid-container {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: auto 1fr auto; /* ヘッダー、ボディ、フッター用の行を定義 */
gap: 20px;
}
次に、子グリッド(.card
)がこの親の行定義を継承するように設定します。
/* 子グリッド(カード)のCSSを修正 */
.card {
display: grid;
grid-template-rows: subgrid; /* 親の行定義を継承! */
grid-row: 1 / 4; /* 親グリッドの1行目から4行目までを専有 */
border: 1px solid #ccc;
border-radius: 8px;
}
grid-template-rows: subgrid;
と指定することで、.card
は自身で新しい行トラックを作るのをやめ、親である.grid-container
が定義した3つの行トラックをそのまま利用するようになります。また、grid-row: 1 / 4;
で、このカードが親グリッドの1行目から3行分(1~4のライン)を占めることを明示します。これにより、全てのカードのヘッダー、ボディ、フッターが、まるで一枚の大きなグリッドの上に乗っているかのように、完璧に整列します。
display: grid と subgrid の違いを比較
display: grid
を指定しただけの通常のネストGridと、subgrid
を使った場合の違いをまとめると、以下のようになります。この表を見ると、subgrid
が親子間の連携に特化した機能であることがよく分かります。
項目 | 通常のネストGrid (display: grid) | Subgrid (grid-template-rows: subgrid) |
---|---|---|
トラック定義 | 子要素自身が独立した行・列のトラックを定義する。 | 親要素の行・列のトラック定義を継承して利用する。 |
アイテムの配置 | 子要素内のアイテムは、その子要素のグリッド内でのみ配置される。 | 子要素内のアイテムも、親要素のグリッドラインを基準に配置できる。 |
gap プロパティ | 親と子、それぞれでgap (溝)を定義できる。 | 親要素のgap が適用される。子要素でgap は指定できない。 |
連携 | 親と子のグリッドは独立しており、直接的な連携はできない。 | 親子のグリッドが連携し、全体のレイアウト整合性が保たれる。 |
主な用途 | コンポーネント単位など、独立したレイアウトを組む場合。 | カード一覧など、入れ子構造全体でアイテムの位置を揃えたい場合。 |
実践!Subgridでカード型UIのレイアウトを完璧に揃える
それでは、先ほどの基本的な使い方を踏まえ、より実践的なカード型UIのレイアウトをSubgrid
を使って構築していく手順を、ステップバイステップで見ていきましょう。最終的には、コンテンツ量が異なる複数のカードが、まるで一つの表のように美しく整列した状態を目指します。
ステップ1:HTML構造の準備
まずはレイアウトの土台となるHTMLを用意します。親コンテナとなるdiv
の中に、複数のカード(article
タグ)を配置するシンプルな構造です。各カードはヘッダー、メインコンテンツ(画像とテキスト)、フッター(ボタン)で構成されています。
<div class="card-list">
<article class="card">
<h3 class="card-title">商品A</h3>
<div class="card-body">
<img src="[https://placehold.co/600x400/f0f0f0/333?text=Image+A](https://placehold.co/600x400/f0f0f0/333?text=Image+A)" alt="商品Aの画像">
<p>この商品の説明文です。テキスト量は標準的です。</p>
</div>
<div class="card-footer">
<a href="#" class="button">詳しく見る</a>
</div>
</article>
<article class="card">
<h3 class="card-title">商品B(タイトルが少し長め)</h3>
<div class="card-body">
<img src="[https://placehold.co/600x400/e0e0e0/333?text=Image+B](https://placehold.co/600x400/e0e0e0/333?text=Image+B)" alt="商品Bの画像">
<p>この商品の説明文は、他のカードよりも多くのテキストを含んでいます。Subgridを使わない場合、このカードの高さに他のカードが引きずられますが、内部の要素の位置は揃いません。</p>
</div>
<div class="card-footer">
<a href="#" class="button">詳しく見る</a>
</div>
</article>
<article class="card">
<h3 class="card-title">商品C</h3>
<div class="card-body">
<img src="[https://placehold.co/600x400/d0d0d0/333?text=Image+C](https://placehold.co/600x400/d0d0d0/333?text=Image+C)" alt="商品Cの画像">
<p>説明文は短いですが、ボタンが2つあります。</p>
</div>
<div class="card-footer">
<a href="#" class="button">詳しく見る</a>
<a href="#" class="button-secondary">カートに入れる</a>
</div>
</article>
</div>
ステップ2:親要素にGridコンテナを設定
次に、カード全体を囲む.card-list
にdisplay: grid
を適用し、レイアウトの骨格を作ります。ここでは、レスポンシブに対応できるように、grid-template-columns
にはrepeat(auto-fit, minmax(300px, 1fr))
を指定します。そして最も重要なのがgrid-template-rows
です。カード内の「タイトル」「ボディ」「フッター」がそれぞれ収まるように、auto 1fr auto
と3つの行を定義します。1fr
を指定したボディ部分が、可変の高さを吸収する役割を担います。
.card-list {
display: grid;
gap: 24px;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
/* カード内の要素に対応する行を定義 */
grid-template-rows: auto 1fr auto;
}
ステップ3:子要素(カード)にSubgridを適用
いよいよSubgrid
の出番です。個々の.card
要素に対して、親の行定義を継承させます。
.card {
display: grid;
/* 親(.card-list)が定義した行トラックを継承 */
grid-template-rows: subgrid;
/* 親の1行目から3行分(4番目のラインまで)を使用する */
grid-row: 1 / 4;
border: 1px solid #ddd;
border-radius: 12px;
overflow: hidden; /* 角丸を画像に適用するため */
box-shadow: 0 4px 8px rgba(0,0,0,0.05);
}
.card-title,
.card-body,
.card-footer {
/* 本来は個々の要素にgrid-rowを指定するが、
HTMLの記述順とグリッドの行が一致しているため不要 */
padding: 16px;
}
.card-title {
font-size: 1.25rem;
background-color: #fafafa;
}
.card-body {
display: flex;
flex-direction: column;
gap: 12px;
}
.card-body img {
width: 100%;
height: auto;
aspect-ratio: 16 / 9;
object-fit: cover;
}
.card-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
background-color: #fafafa;
border-top: 1px solid #ddd;
}
.card
にdisplay: grid
とgrid-template-rows: subgrid
を設定し、grid-row: 1 / 4;
で親グリッドの3つの行すべてを使うことを宣言します。これだけで、魔法のようにすべてのカードのタイトル、ボディ、フッターの高さが揃います。商品Bの説明文が長くても、商品Cのボタンが2つあっても、それぞれのパーツが属する行の高さが全カードで統一されるため、レイアウトが一切崩れません。
gap
プロパティの挙動にも注目
ここで一つ注意点があります。subgrid
を指定した要素では、gap
プロパティは機能しません。溝(gap)の間隔は、すべて親グリッドのgap
設定が適用されます。今回の例では、.card-list
にgap: 24px;
が指定されているため、カード間の縦横の間隔は24px
になります。カード内部の行間のgap
も親グリッドの定義に従うため、もし親側でrow-gap
が指定されていれば、それが適用されることになります。このように、レイアウトの制御権を親グリッドに集約するのがSubgrid
の基本的な考え方です。
知っておくと便利なSubgridの応用テクニックと注意点
Subgridは非常に強力ですが、万能ではありません。その特性を正しく理解し、注意点を把握しておくことで、より効果的に使いこなすことができます。ここでは、Subgridを扱う上での応用知識と、ブラウザ対応、そして代替案について解説します。
暗黙的なグリッドトラックとSubgrid
CSS Gridには、grid-template-columns
やgrid-template-rows
で明示的に定義したトラックの他に、アイテムがはみ出した際に自動的に生成される「暗黙的なグリッドトラック」という概念があります。例えば、3列のグリッドに4つ目のアイテムを配置すると、自動的に2行目に暗黙的な行トラックが作られます。
ここで重要な注意点は、Subgridは明示的に定義されたトラックしか継承できないという点です。親グリッド側でgrid-auto-rows
などを使って暗黙的なトラックのサイズを指定していたとしても、その情報を子グリッドがsubgrid
として受け取ることはできません。Subgridは、あくまでgrid-template-columns
とgrid-template-rows
で定義された、目に見える設計図だけを継承する仕組みだと覚えておきましょう。したがって、Subgridを使いたい場合は、親子関係にあるグリッドのトラックは、必ず明示的に定義する必要があります。
Subgridのブラウザ対応状況(Can I use…)
どんなに便利な機能でも、主要なブラウザで使えなければ意味がありません。気になるSubgridの対応状況ですが、2025年現在、主要なモダンブラウザ(Chrome, Firefox, Safari, Edge)の最新バージョンではすべてサポートされています。
Firefoxがいち早く2019年に対応して以来、他のブラウザも追随し、2023年にSafariが対応したことで、実案件で安心して使える土壌が整いました。Internet Explorerのようなレガシーブラウザを考慮する必要がなければ、積極的にSubgridを活用できるでしょう。最新の対応状況を正確に確認したい場合は、Web制作者の強い味方である「Can I use…」を参照するのがおすすめです。常に最新の情報をチェックする癖をつけておくと良いでしょう。
Subgridが使えない場合の代替案(フォールバック)
Subgridは非常に便利ですが、プロジェクトの要件によっては未対応の古いブラウザをサポートする必要があるかもしれません。そのような場合に備えて、代替案(フォールバック)を知っておくことも重要です。
CSSの@supports
機能を使えば、ブラウザがsubgrid
をサポートしているかどうかを判定し、スタイルを切り替えることができます。
.card {
/* Subgrid未対応ブラウザ向けのスタイル */
display: flex;
flex-direction: column;
}
.card-body {
flex-grow: 1; /* ボディ部分を伸ばして高さを揃える */
}
@supports (grid-template-rows: subgrid) {
/* Subgrid対応ブラウザ向けのスタイル */
.card {
display: grid;
grid-template-rows: subgrid;
grid-row: 1 / 4;
}
}
この例では、まずFlexboxを使って簡易的なレイアウトを定義しておき、@supports
ブロックの中でSubgridが使えるブラウザに対してのみ、Gridベースのスタイルを上書きしています。これにより、古いブラウザでは最低限のレイアウトを保ちつつ、新しいブラウザでは意図した通りの完璧なレイアウトを提供できます。他にも、display: contents;
を使って子要素を親グリッドに直接参加させるトリッキーな方法もありますが、アクセシビリティの問題を引き起こす可能性があるため、Subgridが使える現代においては、素直に@supports
で分岐するのが最も安全で確実な方法と言えるでしょう。
まとめ:Subgridを使いこなし、ワンランク上のCSSレイアウトを実現しよう
今回は、CSS Subgridの基本的な概念から、カード型UIを美しく整える実践的なテクニック、そして利用する上での注意点までを詳しく解説しました。
この記事のポイントを振り返ってみましょう。
- Subgridは、入れ子になったGridレイアウトで親の行・列を継承する機能
- コンテンツ量の違いによるレイアウトの崩れをCSSだけで解決できる
- 使い方は簡単で、
grid-template-rows: subgrid;
のように指定するだけ - 主要なモダンブラウザはすべて対応済みで、実案件で安心して使える
@supports
を使えば、未対応ブラウザへの配慮も可能
Subgridの登場により、これまでJavaScriptで高さを計算したり、display: contents
のようなハックを使ったり、あるいは諦めたりしていた複雑なグリッドアライメントが、非常にシンプルかつ直感的なCSSで書けるようになりました。これは、Webのレイアウト設計における大きな一歩です。
コンポーネントベースの設計が主流の現代において、コンポーネント間の連携を保ちながら美しいレイアウトを実現するSubgridは、今後ますます重要な技術になっていくはずです。ぜひ、あなたの次のプロジェクトからSubgridを取り入れて、ワンランク上のCSSレイアウトを体験してみてください。