📦

CSS Gridサブグリッドの極意:複雑なグリッドレイアウトをシンプルにしてみた!

に公開

本記事のサマリ

CSS Gridのサブグリッド機能は、親グリッドの線を子要素が継承できる仕組みで、従来は複雑だったレイアウトパターンを驚くほどシンプルに実現できます。カードコンポーネントやデータテーブルなど、実際のプロダクト開発でよく遭遇する課題を、わずか数行のCSSで解決できるようになります。ブラウザサポートも十分に進んでおり、モダンな開発現場では積極的に採用を検討したい技術です!

https://github.com/toto-inu/lab-202511-cssgrid

サブグリッドが解決する本当の問題

CSS Gridが登場してから、レイアウト設計は格段に楽になりました。でも実際にプロダクトを作っていると、どうしても解決が面倒な問題に遭遇することがあります。

この記事では、3つの実践的なパターンで従来のアプローチとサブグリッドを比較し、その違いを視覚的に理解できるデモを作成しました。実際に動くコードとともに、サブグリッドの威力を体感していきましょう。

パターン1: カードレイアウト

まずは、最もよく遭遇するパターンから見ていきます。複数のカードが横並びで、それぞれのカード内には画像、タイトル、説明文、ボタンが配置されている。よくある構成ですよね。

カードレイアウトの比較

HTML構造

<div class="card-grid">
  <div class="card">
    <img src="https://picsum.photos/seed/1/300/200" alt="">
    <h4>短いタイトル</h4>
    <p>これは説明文です。</p>
    <button>詳細を見る</button>
  </div>
  <div class="card">
    <img src="https://picsum.photos/seed/2/300/200" alt="">
    <h4>これはとても長いタイトルで複数行になります</h4>
    <p>これは別の説明文で、こちらも少し長めになっています。内容の長さが異なると配置がバラバラになりがちです。</p>
    <button>詳細を見る</button>
  </div>
  <div class="card">
    <img src="https://picsum.photos/seed/3/300/200" alt="">
    <h4>中程度のタイトル</h4>
    <p>短い説明。</p>
    <button>詳細を見る</button>
  </div>
</div>

従来のアプローチ(Flexbox)

.card-grid.traditional {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
}

.card-grid.traditional .card {
  display: flex;
  flex-direction: column;
  border: 2px dashed #64748b;
  border-radius: 8px;
  padding: 1rem;
  background: #fafafa;
}

.card-grid.traditional .card img {
  width: 100%;
  height: 150px;
  object-fit: cover;
  border-radius: 6px;
  margin-bottom: 0.75rem;
}

.card-grid.traditional .card button {
  margin-top: auto; /* ボタンを下に配置するための従来の方法 */
  width: 100%;
}

問題点:

  • 各カードの高さが内容によってバラバラになる
  • ボタンの位置を揃えるために margin-top: autoを使用
  • タイトルや説明文の開始位置は揃わない

サブグリッドアプローチ

サブグリッドを使うと、この問題が一気に解決されます。子要素が親グリッドの線を参照できるようになるからです。

.card-grid.subgrid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: auto auto 1fr auto; /* 4つの行を定義: 画像、タイトル、説明、ボタン */
  gap: 1rem;
}

.card-grid.subgrid .card {
  display: grid;
  grid-template-rows: subgrid; /* 親の行線を継承 */
  grid-row: span 4; /* 4行分のスペースを使用 */
  border: 2px dashed #7c3aed;
  border-radius: 8px;
  padding: 1rem;
  background: #fafafa;
}

.card-grid.subgrid .card img {
  width: 100%;
  height: 150px;
  object-fit: cover;
  border-radius: 6px;
  margin-bottom: 0.75rem;
}

.card-grid.subgrid .card button {
  width: 100%;
  align-self: end; /* サブグリッドのセル内で下揃え */
}

改善点:

  • grid-template-rows: subgridで親の行線を継承
  • すべてのカードで画像、タイトル、説明文、ボタンの位置が完璧に揃う
  • margin-top: autoのようなハックが不要

重要なのは grid-template-rows: subgridの部分です。これによって、子要素(.card)が親要素(.card-grid)のグリッド線を継承するようになります。

実装結果の比較

カードレイアウトの比較

左側の従来のアプローチでは、各カードのボタン位置がバラバラになっていますが、右側のサブグリッドを使った実装では、すべての要素が美しく揃っています。点線の位置を見ると、サブグリッドがどのように親のグリッド線を継承しているかが一目瞭然ですね!

パターン2: データテーブル

サブグリッドの威力がより分かりやすいのは、表形式のレイアウトです。従来のtableタグは使いたくないけれど、きちんと列が揃った表示を実現したい場面で特に有効です。

HTML構造

<div class="data-grid">
  <div class="row header">
    <div>名前</div>
    <div>職種</div>
    <div>経験年数</div>
    <div>アクション</div>
  </div>
  <div class="row">
    <div>田中太郎</div>
    <div>フロントエンドエンジニア</div>
    <div>5年</div>
    <div><button>編集</button></div>
  </div>
  <div class="row">
    <div>鈴木花子</div>
    <div>バックエンドエンジニア</div>
    <div>3年</div>
    <div><button>編集</button></div>
  </div>
</div>

従来のアプローチ(個別グリッド)

.data-grid.traditional {
  display: flex;
  flex-direction: column;
  gap: 1px;
  background: #e5e7eb;
  border-radius: 8px;
  overflow: hidden;
}

.data-grid.traditional .row {
  display: grid;
  grid-template-columns: 200px 250px 100px 100px; /* 各行で個別に定義 */
  background: white;
}

.data-grid.traditional .row.header {
  font-weight: bold;
  background: #f9fafb;
}

.data-grid.traditional .row > div {
  padding: 12px;
  display: flex;
  align-items: center;
}

.data-grid.traditional .row > div:not(:last-child) {
  border-right: 1px solid #e5e7eb;
}

問題点:

  • 各行が独立してグリッドを定義している
  • 列幅を変更したい場合、全ての行で同じ定義を書く必要がある
  • 行ごとに微妙にズレるリスクがある
  • コードの保守性が低い

サブグリッドアプローチ

.data-grid.subgrid {
  display: grid;
  grid-template-columns: 200px 250px 100px 100px; /* 親で一度だけ列幅を定義 */
  gap: 1px;
  background: #e5e7eb;
  border-radius: 8px;
  overflow: hidden;
}

.data-grid.subgrid .row {
  display: grid;
  grid-template-columns: subgrid; /* 親の列線を継承 */
  grid-column: 1 / -1; /* 親の全列にわたって配置 */
  background: white;
}

.data-grid.subgrid .row.header {
  font-weight: bold;
  background: #f9fafb;
}

.data-grid.subgrid .row > div {
  padding: 12px;
  display: flex;
  align-items: center;
}

.data-grid.subgrid .row > div:not(:last-child) {
  border-right: 1px solid #e5e7eb;
}

改善点:

  • 親グリッドで一度だけ列幅を定義
  • 各行は subgridで親の列線を継承
  • すべての行の列が完璧に揃う保証がある
  • 列幅の変更が1箇所で済む

この例では、各行(.row)が親グリッドの列構造を継承しているため、列の幅が完璧に揃います。しかもHTMLは非常にシンプルで、tableタグを使った時の複雑さがありません。

パターン3: ネストレイアウト

サブグリッドの面白いところは、ネストが深くなっても機能することです。実際のWebアプリケーションでは、コンポーネントが何層にも重なることがありますが、そんな場面でも活用できます。

ネストレイアウトの比較

HTML構造

<div class="layout">
  <header class="header">
    <div class="logo">Logo</div>
    <nav class="nav">
      <a href="#">Home</a>
      <a href="#">About</a>
      <a href="#">Contact</a>
    </nav>
    <div class="user-menu">User Menu</div>
  </header>
  <main class="main">
    <div class="content">Main Content Area</div>
    <aside class="sidebar">Sidebar</aside>
  </main>
</div>

従来のアプローチ(個別レイアウト)

.layout.traditional {
  border: 2px solid #e5e7eb;
  border-radius: 8px;
  overflow: hidden;
  min-height: 300px;
}

.layout.traditional .header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
  background: #f9fafb;
  border-bottom: 2px solid #e5e7eb;
}

.layout.traditional .logo {
  font-weight: bold;
  color: #667eea;
  width: 150px;
}

.layout.traditional .nav {
  display: flex;
  gap: 1.5rem;
  flex: 1;
  justify-content: center;
}

.layout.traditional .user-menu {
  width: 150px;
  text-align: right;
  color: #6b7280;
}

.layout.traditional .main {
  display: flex;
  min-height: 250px;
}

.layout.traditional .content {
  flex: 1;
  padding: 1.5rem;
  background: white;
}

.layout.traditional .sidebar {
  width: 200px;
  padding: 1.5rem;
  background: #fafafa;
  border-left: 2px solid #e5e7eb;
}

問題点:

  • ヘッダーとメインで別々のレイアウト設定
  • 列幅の一貫性を保つのが難しい
  • レスポンシブ対応時に複数箇所の調整が必要

サブグリッドアプローチ

.layout.subgrid {
  display: grid;
  grid-template-columns: 150px 1fr 200px; /* 親で3列を定義 */
  grid-template-rows: auto 1fr;
  border: 2px solid #e5e7eb;
  border-radius: 8px;
  overflow: hidden;
  min-height: 300px;
}

.layout.subgrid .header {
  display: grid;
  grid-template-columns: subgrid; /* 親の列線を継承 */
  grid-column: 1 / -1;
  align-items: center;
  padding: 1rem;
  background: #f9fafb;
  border-bottom: 2px solid #e5e7eb;
}

.layout.subgrid .logo {
  font-weight: bold;
  color: #667eea;
}

.layout.subgrid .nav {
  display: flex;
  gap: 1.5rem;
  justify-content: center;
}

.layout.subgrid .user-menu {
  text-align: right;
  color: #6b7280;
}

.layout.subgrid .main {
  display: grid;
  grid-template-columns: subgrid; /* 親の列線を継承 */
  grid-column: 1 / -1;
  grid-row: 2;
}

.layout.subgrid .content {
  grid-column: 1 / 3; /* 最初の2列を使用 */
  padding: 1.5rem;
  background: white;
}

.layout.subgrid .sidebar {
  grid-column: 3;
  padding: 1.5rem;
  background: #fafafa;
  border-left: 2px solid #e5e7eb;
}

改善点:

  • ヘッダーとメインコンテンツが同じ列構造を共有
  • レスポンシブ対応も含めてレイアウトの一貫性を保ちやすい
  • 列幅の変更が親グリッドの1箇所で済む

このように、ヘッダーとメインコンテンツが同じ列構造を共有できるため、レスポンシブ対応も含めてレイアウトの一貫性を保ちやすくなります。

実装結果の比較

ネストレイアウトの比較

左側の従来のアプローチでは、ヘッダーとメインコンテンツが別々のレイアウトシステムを使っています。右側のサブグリッドでは、親グリッドで定義した3列構造を、ヘッダーとメインコンテンツの両方が継承しているのが分かります。この一貫性が、大規模なレイアウトを構築する際に大きな威力を発揮します✨

視覚的な理解のための工夫

デモでは、各パターンに色分けした点線を追加して、グリッド構造を視覚的に理解できるようにしました。

  • 従来版: グレー系や茶色系の点線
  • サブグリッド版: 紫系や緑系、赤系の点線

サブグリッド版では、すべての点線が親グリッドの線に沿って整然と並んでいる様子が確認できます。この視覚的な違いが、サブグリッドの本質を物語っていると思います。

ブラウザサポートと実装上の注意点

サブグリッドのブラウザサポートは、現在のモダンブラウザであれば概ね問題ありません。

  • Firefox 71+
  • Safari 16+
  • Chrome 117+

https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout/Subgrid

レスポンシブ対応の注意点

サブグリッドは親グリッドの構造に依存するため、レスポンシブデザインを考慮する際は、メディアクエリでの列数変更に注意が必要です。

@media (max-width: 900px) {
  .card-grid.traditional,
  .card-grid.subgrid {
    grid-template-columns: 1fr;
  }

  .card-grid.subgrid {
    grid-template-rows: auto; /* 単一列のときはサブグリッドの意味がない */
  }

  .card-grid.subgrid .card {
    grid-row: auto;
    display: flex;
    flex-direction: column;
  }
}

まとめ

CSS Gridのサブグリッドは、複雑になりがちなレイアウト設計を、よりシンプルで保守しやすいものに変えてくれる技術です。

サブグリッドの主なメリット:

  1. 親グリッドの線を子要素が継承できる
  2. 要素の位置が完璧に揃う
  3. コードの保守性が向上する
  4. レスポンシブ対応が楽になる
  5. JavaScriptでの調整が不要になる

今回の3つのパターンで見てきたように、実際のプロダクト開発でよく遭遇する課題を、わずか数行のCSSで解決できるのは本当に便利です。まだ使ったことがない方は、ぜひ小さなプロジェクトから試してみてください。

参考リンク

株式会社StellarCreate | Tech blog📚

Discussion