Firefox や Safari ではすでにサポートされている CSS Subgrid(サブグリッド)ですが、Google Chrome でも 2023 年 9 月上旬リリース予定の 117 よりサポートされます12

Microsoft Edge でのサポートは今のところ未定ですが、Chrome に追従すると考えれば、バージョン 117 がリリースされる 2023 年 9 月中旬以降になるでしょう3

この記事では、まもなく主要なブラウザでサポートされる CSS Subgrid について、実例を取り上げながら特徴や使い方を説明します。

サブグリッドは W3C の仕様では「CSS Grid Layout Module Level 2」に属しており、基本的な特徴としては以下が挙げられます。

  • 子要素の配置を親グリッドのトラックに紐づけることができる
  • サブグリッドを指定した子要素から、さらに直下の子要素にも指定できる
  • 行、列のどちらか一方、もしくは両方を指定できる(grid-template-rowsgrid-template-columns
  • 親グリッドで指定した余白(gap)は上書きできる
CSS Subgrid の図。サブグリッドとして定義することで、親グリッドのトラックに紐づけることができる。図解では、3 行 5 列のトラックを持つ親グリッドに対して、2 行目から 3 行目、2 列目から 4 列目までの範囲に配置している子要素に対して、列のサブグリッドを指定している

以下にコードを抜粋します。

サブグリッドの例
/* 親グリッド */
.parent {
  display: grid;
  grid-template-columns: repeat(5, 80px);
  grid-template-rows: repeat(3, 80px);
  gap: 10px;
}
/* サブグリッド */
.child {
  display: grid;
  grid-column: 2 / 5;
  grid-row: 2 / 4;
  grid-template-columns: subgrid;
}

実例のなかでも一部言及しますが、サブグリッドの注意点としては以下が考えられます。

  • サブグリッドのライン番号はリセットされ、再び 1 から開始する
  • 暗黙のグリッドは生成されない
  • 親グリッドと結びつくため、HTML 構造に対する依存度は高くなる
  • 一足飛びに先祖要素のサブグリッドを定義することはできない

これらを踏まえたうえで、サブグリッドの実例を見ていきましょう。

例1: カードコンポーネント

見出し「例1: カードコンポーネント」

まず、サブグリッドの使用例として一般的な、カードコンポーネントの例を挙げます。

カードコンポーネント グリッド構成
3 列のトラックを持つ親グリッドに対して、4 行のトラックを持つサブグリッドを定義する

このように、カードごとにコンテンツの量が異なるときに、適切なマークアップを維持したまま、隣り合う複数の要素の高さをそろえるには、従来であれば <table> 要素を使用するか、JS で高さを調整するしか方法がありませんでした。

しかし、JS を使用する方法では以下のような問題点や複雑性があります。

  • 高さを算出するために、実行するにはレンダリングを待つ必要がある
  • レンダリング後に実行されるため、レイアウトシフトが発生する(CLS への影響)
  • JS の読み込みと実行により、パフォーマンス面に影響を及ぼす
  • リフローに対応する必要がある(リサイズや文字サイズの変更など)
  • レイアウトによって無効にする必要がある(モバイルでは無効など)

サブグリッドによって、JS を使用せずに高さ揃えが実現できます。以下はライブデモです。

CSS Subgrid に対応している環境でご確認ください。

Live Demo

CSS Subgrid は、お使いのブラウザでは対応していません 🙇‍♂️

このように、たとえ行を折り返したとしても親グリッドのトラックにより、コンポーネント内の要素の高さ揃えは維持されたままになります。ちなみに、4 番目のカードコンポーネントは、あえて見出し下のテキストが抜けた状態にしています。

関連するコードを抜粋します。

カードコンポーネントにサブグリッドを定義した例
<div class="card">
  <article class="card-item">
    <div class="card-image">
      <img src="..." alt="">
    </div>
    <h3 class="card-title">Lorem</h3>
    <p class="card-desc">Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
    <a href="#" class="card-link">Link</a>
  </article>
  <!-- ... -->
</div>

<style>
.card {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 40px;
}
.card-item {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 4;
  grid-row-gap: 16px;
}
.card-link {
  grid-row: end;
}
/* ... */
</style>

カードコンポーネントである .card-item に対して、grid-template-rows: subgrid を指定することで、親グリッドの行方向のトラックをサブグリッドとして使用できます。

続く grid-row: span 4 で、4 行分のトラックを使用することを明示しています。加えて、grid-row-gap: 16px で、親グリッドで指定している gap: 40px を上書きしています。

暗黙的なグリッドは生成されない

見出し「暗黙的なグリッドは生成されない」

ここで注意する点としては、通常のグリッドレイアウトとは異なり、サブグリッドのトラックは暗黙的に生成されないということです。

さきほどの例では、grid-row: span 4 で、4 行分のトラックを明示しましたが、この指定がなかったり、要素数に対して指定するトラック数が不足していると、以下のように要素が重なってしまうため注意が必要です。

サブグリッドにトラックを明示しなかったときのスクリーンショット。スペースが確保されずに要素が重なってしまう
トラックを明示しないと要素が重なってしまう

さらに注意する点としては、直接の子要素でないとサブグリッドとして定義できないので、カード全体を囲う要素と、カードコンポーネント自体の間に要素が入るとサブグリッドは適用されません。

例えば、以下のように「1. 親要素 > 2. 子要素 > 3. 孫要素」の構造で、「3. 孫要素」を「1. 親要素」のサブグリッドとして定義することはできません。

直接の子要素でないとサブグリッドとして定義できない
<!-- 1. 親要素 -->
<div class="parent">
  <!-- 2. 子要素 -->
  <div class="child">
    <!-- 3. 孫要素 -->
    <div class="grandchild">
      <img src="...">
      <h3>...</h3>
      <p>...</p>
      <a href="#">...</a>
    </div>
  </div>
</div>

<style>
/* 1. 親要素 */
.parent {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}
/* 3. 孫要素 */
.grandchild {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 4;
}
</style>

レイアウトによっては、「2. 子要素」をあわせてサブグリッドに定義することで解消できますが、カードコンポーネントの例では行トラックの生成がうまくいきません。

この例でも対応するには、「2. 子要素」に display: contents を指定するのが近道ですが、この方法にも気をつける点があるので、それらを踏まえたうえで検討する必要があります。

なお、後述するポジショニングマップの例では、中間要素をサブグリッドとして定義して対応しています。

中間の要素に display: contents を指定すると、グリッドアイテムとして認識されなくなるので、HTML の構造を維持したまま、その下の要素(孫要素)をサブグリッドとして定義することができます。

.parent {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}
.child {
  display: contents;
}
.grandchild {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 4;
}

しかし、display: contents を指定した要素にはボックスが生成されないため、余白や背景を指定できないので、活用できる場面は限定されます。

また、display: contents には、アクセシビリティに関するブラウザ上のバグが存在しており、その多くは修正されましたが、いくつかのバグは残ったままです。

カードコンポーネントの例のように、<div> に指定する分には問題なさそうですが、念のため留意しておいたほうがよいでしょう。

例2: リストの行頭揃え

見出し「例2: リストの行頭揃え」

次に、リストの行頭揃えの例を見ていきます。さきほどのカードコンポーネントでは行トラックをそろえましたが、今度は列トラックをそろえます。

リストコンポーネント グリッド構成
3 列のトラックを持つ親グリッドに対して、サブグリッドを定義する

以下はライブデモです。

Live Demo

CSS Subgrid は、お使いのブラウザでは対応していません 🙇‍♂️

  1. FIREBIRD
  2. DOLPHIN DOS
  3. LION
  4. FROGGY
  5. ELECTRIC SHOCK
  6. TUNDRA
  7. BOOK JACKET
  8. MOONLITE DREAMS
  9. CHOCOMINT
  10. MONO

この例では .list-item に対して、grid-template-columns: subgrid を指定し、列方向のグリッドトラックをサブグリッドとして使用しています。

行頭の番号は CSS の ::before 擬似要素に counter() 関数を使用して生成しており、意図的に桁数を変えていますが、この桁数の変化によって幅が異なる場合でもサブグリッドによってラインをそろえることができます。

リストコンポーネントにサブグリッドを指定した例
<ol class="list">
  <li class="list-item">
    FIREBIRD
    <img src="..." alt="">
  </li>
  <!-- ... -->
</ol>

<style>
.list {
  display: grid;
  grid-template-columns: auto 1fr 80px;
  counter-reset: list-counter;
}
.list-item {
  display: grid;
  grid-template-columns: subgrid;
  grid-column: 1 / -1;
  counter-increment: list-counter;
}
.list-item::before {
  content: counter(list-counter)'.';
}
/* ... */
</style>

このように、サブグリッドで列トラックをそろえる応用例として、以下のようなニュース一覧のレイアウトも考えられそうです。

Live Demo

CSS Subgrid は、お使いのブラウザでは対応していません 🙇‍♂️

  • 2023/7/31

    News

    Lorem ipsum dolor sit amet, consectetur adipisicing elit.

  • 2023/3/1

    Information

    Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

  • 2022/12/21

    Press Release

    Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

例3: ポジショニングマップ

見出し「例3: ポジショニングマップ」

最後に、行、列の両軸をサブグリッドに定義する例として、ポジショニングマップを実装します。

ポジショニングマップ グリッド構成
10 行、10 列のトラックを持つ親グリッドに対してサブグリッドを定義

以下はライブデモです。

Live Demo

CSS Subgrid は、お使いのブラウザでは対応していません 🙇‍♂️

  • FIREBIRD

  • DOLPHIN DOS

  • ELECTRIC SHOCK

  • FROGGY

  • LION

この例では、親グリッドとグリッドに配置したいアイテムとの間に中間要素が存在します。

以下は抜粋したコードですが、子要素(.map-list)と孫要素(.map-item)に対して、grid: subgrid / subgrid を指定し、行・列ともにサブグリッドを定義しています。

行、列の両軸にサブグリッドを指定した例
<div class="map-container">
  <!-- 親要素 -->
  <div class="map">
    <!-- 子要素 -->
    <ul class="map-list">
      <!-- 孫要素 -->
      <li class="map-item">
        <p>FIREBIRD</p>
        <img src="..." alt="">
      </li>
      <!-- ... -->
    </ul>
  </div>
</div>

<style>
.map-container {
  container: map / inline-size;
}
/* 親要素 */
.map {
  display: grid;
  grid: repeat(10, 10cqi) / repeat(10, 10cqi);
}
/* 子要素 */
.map-list {
  display: grid;
  grid: subgrid / subgrid;
  grid-column: 1 / -1;
  grid-row: 1 / -1;
}
/* 孫要素 */
.map-item {
  display: grid;
  grid: subgrid / subgrid;
}
.map-item > * {
  grid-column: 1 / -1;
}
.map-item:nth-child(1) {
  grid-column: 2 / span 2;
  grid-row: 1 / span 3;
}
/* ... */
</style>

ちなみに、正方形の可変グリッドを生成するために、grid: repeat(10, 10cqi) / repeat(10, 10cqi) の指定で、コンテナクエリの単位(cqi)を使用しています。

この記事では、実例を挙げながら CSS Subgrid の基本的な使い方を説明しました。

サブグリッドについては、なんとなく概念を理解しているつもりでしたが、実際にコードを書いてみるとうまくいかない場面があり、やはり手を動かすことは重要だと改めて実感しました。

今後、使っていくうちに、より有用な使い方や新たな課題が見つかるかもしれませんので、引き続き実践をとおして、サブグリッドの使い方を考えていきます。

本記事の作成にあたり、以下のウェブページを参考にしました。

脚注

  1. CSS Subgrid | Can I use…

  2. CSS Subgrid | Chrome Platform Status

  3. Microsoft Edge リリース スケジュール