データ表示

テーブル

解説あり

Table

表データ。table 要素と見出しセルの関連付けが読み上げの鍵。

テーブル(表)とは?

テーブル(表)は、行と列で整理された静的なデータを見せるための UI です。 料金の比較表、売上の集計、スペック一覧、時間割など、 「縦と横の見出しで意味づけされた数値や項目」を並べる場面で使います。

ここで扱うのは操作するためのウィジェットではなく、読むための表です。 セルを編集したり矢印キーで動き回ったりする対話的なデータグリッドが必要なときは、 別パターンの grid を使います。普通の表は、HTML 標準の <table> を 正しく書くだけで、ほとんどのアクセシビリティが手に入ります。

なぜアクセシビリティが大事なの?

目で見ている人は、ある数値が「どの行・どの列のものか」を、 視線を上や左の見出しに走らせて一瞬で把握できます。 この「セルと見出しの結びつき」を、見えない人にも届けるのがポイントです。

解決策はシンプルです。<table><caption><th scope> を使うこと。それだけで関連付けが成立します。

ライブデモ(推奨実装)

下は、列見出し(scope="col")と行見出し(scope="row")を 正しく付けたセマンティックな表です。見た目は普通の表ですが、 支援技術での読まれ方がまったく違います。

セマンティックな表(caption + scope 付き)
店舗別・四半期の売上(単位:万円)
店舗Q1Q2Q3
渋谷店120148165
梅田店98110132
博多店7684101

試してみよう:スクリーンリーダーの表ナビゲーション(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 グリッドで「見た目だけ表」にしたものです。目で見るぶんには表に見えますが、支援技術にはただの数字の羅列に聞こえ、 どの数字がどの店舗・どの四半期なのかまったく伝わりません

div を並べただけの「見た目だけ表」
店舗
Q1
Q2
Q3
渋谷店
120
148
165
梅田店
98
110
132

試してみよう:スクリーンリーダーで読ませると「店舗 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 Pattern — W3C APG(新しいタブで開きます)