データ表示
テーブル
解説ありTable
表データ。table 要素と見出しセルの関連付けが読み上げの鍵。
テーブル(表)とは?
テーブル(表)は、行と列で整理された静的なデータを見せるための UI です。 料金の比較表、売上の集計、スペック一覧、時間割など、 「縦と横の見出しで意味づけされた数値や項目」を並べる場面で使います。
ここで扱うのは操作するためのウィジェットではなく、読むための表です。 セルを編集したり矢印キーで動き回ったりする対話的なデータグリッドが必要なときは、 別パターンの grid を使います。普通の表は、HTML 標準の <table> を 正しく書くだけで、ほとんどのアクセシビリティが手に入ります。
なぜアクセシビリティが大事なの?
目で見ている人は、ある数値が「どの行・どの列のものか」を、 視線を上や左の見出しに走らせて一瞬で把握できます。 この「セルと見出しの結びつき」を、見えない人にも届けるのがポイントです。
- スクリーンリーダーを使う人。正しい
<table>なら、 セルに移動したときに「渋谷店, Q2, 148」のように行見出し・列見出しとセルの値をセットで読み上げてくれます。 これにより、表の中を縦横に移動しても迷子になりません。 - 一方、
<div>を並べて「見た目だけ表」にすると、 支援技術にはただの数字の羅列に聞こえます。 「120」が何の数字なのか、どの店舗のどの四半期なのかが一切分かりません。
解決策はシンプルです。<table> + <caption> +<th scope> を使うこと。それだけで関連付けが成立します。
ライブデモ(推奨実装)
下は、列見出し(scope="col")と行見出し(scope="row")を 正しく付けたセマンティックな表です。見た目は普通の表ですが、 支援技術での読まれ方がまったく違います。
| 店舗 | Q1 | Q2 | Q3 |
|---|---|---|---|
| 渋谷店 | 120 | 148 | 165 |
| 梅田店 | 98 | 110 | 132 |
| 博多店 | 76 | 84 | 101 |
試してみよう:スクリーンリーダーの表ナビゲーション(VoiceOver なら ⌃⌥ + 矢印)でセルを移動すると、各セルで行見出し・列見出しが一緒に読み上げられます。
ポイント
スクリーンリーダー(macOSなら ⌘+F5 で VoiceOver)をオンにして 「160」のセルに移動すると、「渋谷店, Q3, 165」のように行見出し・列見出し・値がセットで読み上げられます。 これが <th scope> による関連付けの効果です。
キーボード操作
静的な表は操作ウィジェットではありません。 そのため Tab で止まるフォーカス位置(タブストップ)はなく、 自分で矢印キーの処理(roving tabindex)を書く必要もありません。 下記は、スクリーンリーダーがブラウズ(読み上げ)モードで提供する 「表ナビゲーション」のコマンドで、支援技術側の機能です。
| キー | 動作 | 提供元 |
|---|---|---|
| ⌃⌥ + ← → ↑ ↓ | 表の中を1セルずつ移動(VoiceOver の表ナビゲーション)。移動先で見出しと値を読み上げる | 支援技術側 |
| 表ナビゲーション開始(VO: ⌃⌥⌘+T 等) | 表の読み上げ・ナビゲーションモードに入る(リーダーごとに操作は異なる) | 支援技術側 |
| Tab | 表自体はタブストップにならない。表内にリンクやボタンがあればそこへ移動する | ブラウザ標準 |
補足
具体的なキーは NVDA / JAWS / VoiceOver などリーダーごとに異なります。 重要なのは「正しい <table> マークアップさえあれば、 これらの表ナビゲーションが自動的に使えるようになる」という点です。 実装者がキー処理を書く必要はありません。
必要な WAI-ARIA / ロール
ネイティブの <table> を使う限り、ARIA ロールはほとんど不要です(必要なロールは要素が自動で持っています)。 大切なのは「正しい HTML 要素」を選ぶことです。
| 付ける場所 | 要素 / 属性 | 意味 |
|---|---|---|
| 表全体 | <table> | 表であることを伝える。role="table" 等は自動で付くので書かない。 |
| 表の見出し | <caption> | 表に名前(タイトル)を付ける。スクリーンリーダーが「〜の表」と読み、表一覧でも識別できる。 |
| 列の見出しセル | <th scope="col"> | その列に属するセルの見出しだと示す。同じ列のセルに関連付けられる。 |
| 行の見出しセル | <th scope="row"> | その行に属するセルの見出しだと示す。同じ行のセルに関連付けられる。 |
| 見出し / 本体 | <thead> / <tbody> | 見出し行とデータ行を構造的に区別する。 |
| データセル | <td> | 実データの値。<th> と書き分けることが関連付けの前提。 |
補足
複雑な表(見出しが複数段になる等)では、<th id> と<td headers> で明示的にひも付けできます。 ただしまずは シンプルな表を scope で正しく書くことを優先しましょう。 表は単純であるほどアクセシブルです。
実装:推奨パターン(Good)
良い例 / 推奨
<table> + <caption> + <thead>/<tbody> を使い、 列見出しに scope="col"、行見出しに scope="row" を付けます。
マークアップ(JS は不要。正しい要素を選ぶだけ):
<table>
<caption>店舗別・四半期の売上(単位:万円)</caption>
<thead>
<tr>
<th scope="col">店舗</th>
<th scope="col">Q1</th>
<th scope="col">Q2</th>
<th scope="col">Q3</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">渋谷店</th>
<td>120</td>
<td>148</td>
<td>165</td>
</tr>
<tr>
<th scope="row">梅田店</th>
<td>98</td>
<td>110</td>
<td>132</td>
</tr>
<tr>
<th scope="row">博多店</th>
<td>76</td>
<td>84</td>
<td>101</td>
</tr>
</tbody>
</table>補足
左上の「店舗」セルも <th scope="col"> にしている点に注目してください。 これは「下に並ぶ店舗名(行見出し)の列ラベル」として機能します。 各行の店舗名は <th scope="row"> にすることで、 その行のデータ全部の見出しになります。
アンチパターン(Bad)
下は <div> を並べて CSS グリッドで「見た目だけ表」にしたものです。目で見るぶんには表に見えますが、支援技術にはただの数字の羅列に聞こえ、 どの数字がどの店舗・どの四半期なのかまったく伝わりません。
試してみよう:スクリーンリーダーで読ませると「店舗 Q1 Q2 Q3 渋谷店 120 148 165 …」と平坦に流れるだけで、行・列の見出しとセルの関係が失われます。
<!-- ❌ アンチパターン -->
<!-- 見た目は表だが <table>/<th>/scope が無く、意味が伝わらない -->
<div class="grid">
<div class="cell head">店舗</div>
<div class="cell head">Q1</div>
<div class="cell head">Q2</div>
<div class="cell head">Q3</div>
<div class="cell">渋谷店</div>
<div class="cell">120</div>
<div class="cell">148</div>
<div class="cell">165</div>
<div class="cell">梅田店</div>
<div class="cell">98</div>
<div class="cell">110</div>
<div class="cell">132</div>
</div>悪い例 / 避ける
この実装の問題点:
- 見出しとセルが関連付かない —
<th>/scopeが無いので、「120」がどの店舗・どの四半期かが伝わらない。 - 名前(タイトル)が無い —
<caption>が無く、何の表かが分からない。 - 表ナビゲーションが効かない —
<table>ではないので、スクリーンリーダーの表移動コマンドが使えない。 - ただの数字の羅列に聞こえる — 構造が無く、上から平坦に読み上げられるだけで意味を再構成できない。
ポイント
どうしても <div> で組まざるを得ない場合は、role="table"・role="row"・role="columnheader"・role="rowheader"・role="cell" をすべて自前で付ける必要があります。 最初から <table> を使えば、その大半がタダで手に入ります。
実装チェックリスト
- データの表はネイティブの
<table>で組んでいる(<div>の格子ではない) <caption>で表に名前(タイトル)を付けている- 列の見出しセルは
<th scope="col">である - 行の見出しセルは
<th scope="row">である - 見出し行は
<thead>、データ行は<tbody>で構造化している - レイアウト目的では
<table>を使っていない(表は「データ」のときだけ) - 対話的にセル間を操作する必要があるなら、これではなく
gridパターンを使う