【CSS Tips】tableタグを使わずにテーブルレイアウトを作る

## はじめに

この記事を書いた時点では、`Subgrid`のサポートされているブラウザが限られていましたが、2024年3月現在では主要なブラウザでサポートがされています。

そのため、[方法③ Subgridを使う](#方法-subgridを使う)がおすすめの方法です。

["subgrid" | Can I use... Support tables for HTML5, CSS3, etc](https://caniuse.com/?search=subgrid)

## これは何

Webを作っている際に、「見た目はテーブルレイアウトを使って組むと楽そう」だけど、「実際にはテーブルではない」要素を作りたいことがちょこちょことあります。

以下のようなUIです。

![3つの要素が横並びになっている行が、3つ並んでいる。それぞれの行の同じ順番の要素は、開始位置が揃っている](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577501/a3e7d030-a866-7942-2c73-a801beb4d806.png)

こうした要素をtable要素を使ってデザインしてしまうと、支援技術を使用しているユーザーが意味を適切に理解できなかったり、テーブルを読み取る操作が複雑なために内容を読み取れないことがあります。

:::note
テーブル以外でも、基本的に使用するタグは「実現したい見た目」ではなく、「要素が持つ意味」に応じて適したものを使いましょう。
文字サイズを変えたいからheadingを使ったりボタンをdivで作ったりすると、支援技術などで誤った伝わり方をしたり、適切に操作できないことがあります。
:::

こうした時にtable要素を使わずにマークアップ・スタイリングする方法をまとめてみました。

## 方法① `Flexbox`を使う

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577501/22d95385-3a97-a3a0-03e9-a79670831d20.png)

### 手順

1. 各行を`div`などで囲い、`display: flex;`を指定する
1. 各列に`width`や`flex`の値を指定し、横幅が揃うようにする

### :thumbsup: メリット

- 1行ごとにマークアップできる（情報構造をマークアップで表現できる）

### :thumbsdown: デメリット

- 要素のサイズに応じて各列の横幅を決めることができない
- 各列の位置を揃えるには、要素の内容をに応じてwidthを指定しておくか、要素の位置を取得して計算する必要がある

### 個人的な所感

内容に応じて柔軟に列の幅を変える必要がないのであれば、この方法で問題ないと思います。
ただし`width`で固定幅を指定する際は、要素が変わったときに表示崩れなどを起こさないよう注意が必要です。
`min-width`などで指定しておくと、翻訳などをして幅が伸びた時の保険になるのでおすすめです。

### マークアップとスタイリング

```html
<section>
  <div class="flex-row">
    <div class="flex-item">1</div>
    <div class="flex-item">2</div>
    <div class="flex-item">3</div>
  </div>
  <div class="flex-row">
    <div class="flex-item">4</div>
    <div class="flex-item">5</div>
    <div class="flex-item">6</div>
  </div>
</section>
```

```css
.flex-row {
  display: flex;
  gap: 4px;
}

.flex-row+.flex-row {
  margin-top: 4px;
}

.flex-item {
  background: #eee;
  padding: 4px;
}

.flex-item:nth-child(1) {
  width: 40px;
}

.flex-item:nth-child(2) {
  flex: 2;
}

.flex-item:nth-child(3) {
  flex: 1;
}
```

### :arrow_down_small: サンプル

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="jOQWwwq" data-user="kabechiyo" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/kabechiyo/pen/jOQWwwq">
  flex</a> by kabechiyo_shunkaaizawa (<a href="https://codepen.io/kabechiyo">@kabechiyo</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>


## 方法② `grid`を使う

### 手順

1. 要素全体を`div`などで囲い、`display: grid;`と`grid-template-columns: repeat([列の数], auto);`を指定する

### :thumbsup: メリット

- 要素のサイズに応じて各列の横幅を決めることができる

### :thumbsdown: デメリット

- 1行ごとにマークアップできない（情報構造をマークアップで表現できない）

### 個人的な所感

1行ごとに情報の区切りがないのであれば、この方法がシンプルに実装できます。
1行ごとにマークアップできないため、1行ごとに情報の区切りがある場合は方法①を使う方が良いと思います。

### マークアップとスタイリング

```html
<div class="grid-container">
  <div class="grid-item">1</div>
  <div class="grid-item">2</div>
  <div class="grid-item">3</div>
  <div class="grid-item">4</div>
  <div class="grid-item">5</div>
  <div class="grid-item">6</div>
</div>
```

```css
.grid-container {
  display: grid;
  gap: 4px;
  grid-template-columns: repeat(3, auto);
}

.grid-item {
  background: #eee;
  padding: 4px;
}
```

### :arrow_down_small: サンプル

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="dyQGRJQ" data-user="kabechiyo" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/kabechiyo/pen/dyQGRJQ">
  grid</a> by kabechiyo_shunkaaizawa (<a href="https://codepen.io/kabechiyo">@kabechiyo</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## 方法②-2 `grid`と`display: contents`を使う

:::note
[コメント](https://qiita.com/kabechiyo13/items/f888f6f1a7e31df14395#comment-75c6c2840633b5270f31)で @juner さんに`display: contents`を使う方法を教えていただいたため、追記しました
:::

### 手順

1. 要素全体を`div`などで囲い、`display: grid;`と`grid-template-columns: repeat([列の数], auto);`を指定する
1. 各行を`div`などで囲い、`display: contents;`を指定する


### :thumbsup: メリット

- 要素のサイズに応じて各列の横幅を決めることができる

### :thumbsdown: デメリット

- `display: contents;`を指定した要素のroleがアクセシビリティツリー上で無視される
    - ![`display: contents;`を指定した要素のroleがIgnoredになっている](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577501/d50acf60-8199-87b2-9e73-d94ad8bbb8a9.png)
    - Ref [表示によりアクセスしやすくなったマークアップ: content | 隠れブログ](https://hidde.blog/more-accessible-markup-with-display-contents/)

### 個人的な所感

後述の`Subgrid`と近い挙動を実現でき、使い勝手が良いです
行をwrapするタグのロールが失われてしまうので、意味を持たせる場合は方法①を使う方が良いと思います。
この仕様自体はバグらしいので、修正を待ちたいです。

### マークアップとスタイリング

```html
<div class="grid-container">
  <div class="subgrid-container">
    <div class="grid-item">1</div>
    <div class="grid-item">2</div>
    <div class="grid-item">3</div>
  </div>
  <div class="subgrid-container">
    <div class="grid-item">4</div>
    <div class="grid-item">5</div>
    <div class="grid-item">6</div>
  </div>
</div>

```

```css
.grid-container {
  display: grid;
  gap: 4px;
  grid-template-columns: repeat(3, auto);
}

.subgrid-container {
  display: contents;
}

.grid-item {
  background: #eee;
  padding: 4px;
}
```

### :arrow_down_small: サンプル

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="NWExvQz" data-user="kabechiyo" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/kabechiyo/pen/NWExvQz">
  Untitled</a> by kabechiyo_shunkaaizawa (<a href="https://codepen.io/kabechiyo">@kabechiyo</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## 方法③ `Subgrid`を使う

:::note alert
~~この方法は現在サポートされているブラウザが限られており、実用的ではありません。~~
2024年3月現在、主要ブラウザでサポートがされており使うことができるようになりました :tada: 
["subgrid" | Can I use... Support tables for HTML5, CSS3, etc](https://caniuse.com/?search=subgrid)
:::

### 手順

1. 要素全体を`div`などで囲い、`display: grid;`と`grid-template-columns: repeat([列の数], auto);`を指定する
1. 各行に`display: grid;`, `grid-template-columns: subgrid;`, `grid-column: span [列の数];`を指定する


### :thumbsup: メリット

- 1行ごとにマークアップできる（情報構造をマークアップで表現できる）
- 要素のサイズに応じて各列の横幅を決めることができる

### :thumbsdown: デメリット

- ~~最初に書いた通り、サポートされているブラウザが限られているため実用的でない~~

### 個人的な所感

~~サポートブラウザが限られている以外は、方法①・②よりも使い勝手が良いので、早く使えるようになってほしい..と思ってます~~

追記：使えるようになりました！

### マークアップとスタイリング

```html
<div class="grid-container">
  <div class="subgrid-container">
    <div class="grid-item">1</div>
    <div class="grid-item">2</div>
    <div class="grid-item">3</div>
  </div>
  <div class="subgrid-container">
    <div class="grid-item">4</div>
    <div class="grid-item">5</div>
    <div class="grid-item">6</div>
  </div>
</div>
```

```css
.grid-container {
  display: grid;
  gap: 4px;
  grid-template-columns: repeat(3, auto);
}

.subgrid-container {
  grid-column: span 3;
  display: grid;
  grid-template-columns: subgrid;
}

.grid-item {
  background: #eee;
  padding: 4px;
}
```

### :arrow_down_small: サンプル（Firefoxでご覧ください）

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="oNQbwdQ" data-user="kabechiyo" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/kabechiyo/pen/oNQbwdQ">
  subgrid</a> by kabechiyo_shunkaaizawa (<a href="https://codepen.io/kabechiyo">@kabechiyo</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>