大きなデータテーブルは、列がはみ出したり、縮小して判読できなくなったりするため、小さな画面では読めなくなることがよくあります。このガイドでは、固定位置を使用してスクロール中にヘッダーを表示したままにする方法と、スペースが限られている場合にテーブルをモバイル フレンドリーな「積み上げ」レイアウトに変換する方法について説明します。
おすすめの方法
レスポンシブ テーブルの核となるのは、データセルとそのヘッダーの関係を維持することです。
- セマンティック基盤: 標準の
<table>要素を<thead>、<tbody>、<th>要素とともに使用します。 - 固定コンテキスト: 列ヘッダーと行ヘッダーの両方に
position: stickyを適用します。これにより、ユーザーがどの方向にどれだけスクロールしても、データが何を表しているのかというコンテキストを見失うことがなくなります。 - 適応型変換:
@mediaクエリの代わりに@containerクエリを使用します。これにより、テーブルはビューポート全体ではなく、独自の幅(サイドバーや狭いダッシュボード ウィジェットに配置された場合など)に基づいて適応できます。 - ラベルの挿入: 積み上げレイアウトでは、
<thead>が非表示になり、::before疑似要素と CSS 変数を使用して、アクセス可能なヘッダーが各セルに挿入されます。
実装の手順
1. マークアップとレイアウト
標準のセマンティック ヘッダーを使用してテーブルを構成します。.table-wrapper を使用してオーバーフローを処理し、クエリのコンテナを提供します。コンテナに tabindex="0" 属性を追加して、キーボードのみを使用しているユーザーがテーブルを常にスクロールできるようにします。
ラッパーに role="region" 属性を指定して、その目的を伝えます。aria-labelledby(テーブルにキャプションや名前を付ける既存の要素の id 属性を参照)または aria-label 属性を使用して、ユーザー補助機能用の名前を指定します。
<div tabindex="0" role="region" aria-labelledby="employee-table-caption" class="table-wrapper">
<table>
<caption id="employee-table-caption">Current employee directory</caption>
<thead>
<tr>
<th>Employee</th>
<th>Role</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<!-- Row header remains sticky horizontally on desktop,
and becomes a sticky card header on mobile -->
<th>Alex Rivera</th>
<td>Engineer</td>
<td>Active</td>
</tr>
</tbody>
</table>
</div>
2. 固定ヘッダーを構成する
スクロールを有効にして、ヘッダーを固定します。論理プロパティと明示的な z-index 値を使用して、重ね順を管理します。
.table-wrapper {
overflow: auto;
max-inline-size: 100%;
max-block-size: min(500px, 80vh);
container-type: inline-size;
}
table {
border-collapse: separate;
border-spacing: 0;
}
/* Sticky column headers (desktop) */
thead {
position: sticky;
inset-block-start: 0;
z-index: 1; /* Above sticky row headers */
}
/* Sticky row headers (desktop) */
tbody th {
position: sticky;
inset-inline-start: 0;
background: white; /* Required to cover background during scroll */
}
3. レスポンシブな積み上げレイアウト
スペースが限られている場合は、元のヘッダー行を非表示にして、テーブルをカードに変換します。疑似要素を使用してラベルを挿入します。代替テキストはテキスト以外のコンテンツにのみ指定してください。そうしないと、スクリーン リーダーでテキスト コンテンツとユーザー補助機能用の名前の両方が読み上げられる可能性があります。
@container (width < 600px) {
table, thead, tbody, tr, th, td {
display: block;
}
thead {
/* MANDATORY: Hide column header from layout and screen readers. */
display: none
}
tr {
margin-block-end: 1.5rem;
border: 1px solid #ccc;
}
/* Sticky row headers at the top of each block/card */
tbody th {
position: sticky;
inset-block-start: 0;
z-index: 2;
background: #eee;
}
/* Define column labels as CSS variables (often set via JS) */
table {
--label-1: "Employee";
--label-2: "Role";
--label-3: "Status";
}
/* Inject labels */
td:nth-child(2)::before {
/* MANDATORY: If using non-text content, provide the accessible name (after the /) which ensures screen readers hear the text alternative. */
content: var(--label-2) ": ";
}
/* MANDATORY: Map the label for each column */
td:nth-child(3)::before {
content: var(--label-3) ": ";
}
}