📕

【HTML】dl, dt, ddで組みたくなる表、tableにするのがいいかもね(スクリーンリーダーと検索エンジンのために)

2024/04/07に公開

はじめに結論から

こういう dl で組みたくなる表(キーと値が対応付けられた、メタデータを示す表)は、table で組むことをおすすめします!

スクリーンショット:3行2列の表。1列目が見出し、2列目がそれに対する内容になっている。

理由

  • スクリーンリーダーで読み上げたときに意図した項目数・役割が読み上げられる(アクセシビリティ上のメリット)
  • 名前と値が 1 対 1 対応であるという情報構造が機械可読である(SEO 上のメリット)
  • 必要に応じて、追加の列や、見出し行を追加できる(拡張性)

比較表

dl (dt, dd) ul (li, hn, p) table (tr, th, td)
項目数の読み上げ 🔺
見出しと値の区別 🔺
名前-値の構造の機械可読性
(≒SEO上のメリット)
❌️
見出し行
追加の列

🔺…スクリーンリーダーによる

詳しくは以降で説明します。

想定する表の内容

この記事の議論では、名前と値の組が複数並んでいる、メタデータの表を想定します。
プログラミング言語でいうところの、連想配列 (Map, Dictionary, JS では Object) の構造に相当します。
具体的には以下のようなものです。

  • 会社概要(「会社名:〇〇、所在地:〇〇、資本金:〇〇、…」)
  • 商品の仕様表(「商品名:〇〇、価格:〇〇、サイズ:〇〇、…」)
  • 人やキャラクターのプロフィール(「名前:〇〇、年齢:〇〇、出身地:〇〇、…」)

この記事中では、ポケモン(カビゴン)の図鑑情報を例に取り上げます。

比較する手段

📢 今回出場する選手たちは以下の 3 チーム!

  1. dl (div, dt, dd)
  2. ul (li, hn, p)
  3. table (tr, th, td)

先にそれぞれの実装例を紹介して、その後比較していきます。
なお、スタイリング(CSS)に関してはいずれの方法でも問題がないと判断して、特に比較は行っていません。

エントリーNo.1 dl

dl要素を使用する場合、次のような HTML 構造で作成します。

<dl>
  <div>
    <dt>図鑑ナンバー</dt>
    <dd>0143</dd>
  </div>
  <div>
    <dt>タイプ</dt>
    <dd>ノーマル</dd>
  </div>
  <div>
    <dt>説明文</dt>
    <dd>1にちに たべものを 400キロ たべないと きが すまない。たべおわると ねむってしまう。</dd>
  </div>
</dl>
  • 全体を dl 要素で囲む
  • スタイリングのため、各行を div 要素で囲む
  • dt 要素で、見出しを示す
  • dd 要素で、見出しに対応する値を示す

使い方が適切かどうか、 dl要素のHTML仕様日本語訳)を参照しておきます。

Name-value groups may be terms and definitions, metadata topics and values, questions and answers, or any other groups of name-value data.

名前-値グループは、用語とその定義、メタデータの見出しや値、質問と回答、または名前-値のデータのグループであってもよい。

とあり、この用途は「メタデータの見出しや値」にあたりそうなので、妥当でしょう。

エントリーNo.2 ul

ul を使用する場合、次のような構造を想定します。

<ul>
  <li>
    <h3>図鑑ナンバー</h3>
    <p>0143</p>
  </li>
  <li>
    <h3>タイプ</h3>
    <p>ノーマル</p>
  </li>
  <li>
    <h3>説明文</h3>
    <p>1にちに たべものを 400キロ たべないと きが すまない。たべおわると ねむってしまう。</p>
  </li>
</ul>
  • 全体を ul 要素で囲む
  • 各行を li 要素で囲む
  • h3 要素で、見出しを示す(見出しレベルは状況に応じて)
  • p 要素で、見出しに対応する値を示す

エントリーNo.3 table

次のような構造を想定します。

<table>
  <caption>カビゴンの情報</caption>
  <tbody>
    <tr>
      <th>図鑑ナンバー</th>
      <td>0143</td>
    </tr>
    <tr>
      <th>タイプ</th>
      <td>ノーマル</td>
    </tr>
    <tr>
      <th>説明文</th>
      <td>1にちに たべものを 400キロ たべないと きが すまない。たべおわると ねむってしまう。</td>
    </tr>
  </tbody>
</table>
  • 全体を table 要素で囲む
  • caption 要素で、テーブルの内容を説明するテキストを指定する
    • WAI-ARIA の仕様上、table ロールはアクセシブルな名前が必須とされているため記載。HTML 仕様においては、なくても問題はありません
  • tbody 要素で、テーブルの本体を示す
  • tr 要素で、行を示す
  • th 要素で、見出しを示す
    • scope="row" 属性で、見出しの範囲を明示することも可能。この構造の場合は scope 属性を省略しても行ヘッダーとして認識されるため省略
  • td 要素で、見出しに対応する値を示す

第1試合 - スクリーンリーダーで読み上げ

以前読み上げの比較をした CodePen がありますので、参考に載せておきます。

dl は意図した読み上げにならない

dl で組んだ表をスクリーンリーダーで読み上げてみると、とある問題が判明します。
以下に NVDA で読み上げた結果を示します。

リスト  6項目
図鑑ナンバー
0143
タイプ
ノーマル
説明文
1にちに たべものを 400キロ たべないと きが すまない。たべおわると ねむってしまう。
リストの外

注目するポイントとしては以下の通りです。

  • ✅「リスト」と読み上げられ、並列の項目が並んでいることがわかる
  • ❌(3 項目のはずなのに)6項目と読み上げられてしまう
  • 見出しと値が区別されない

本来、「図鑑ナンバー」「タイプ」「説明文」という 3 項目があることを伝えたいのですが、6 項目と読み上げられてしまいます。
これは、dt の数ではなく dtdd の合計を読み上げてしまうことによります。

また、見出しと値を区別する読み上げがされていないので、今読んでいるのが見出しなのか値なのかわかりません(「図鑑ナンバー」「タイプ」「説明文」という内容からなんとなくわかるとはいえ)。

MDN に以下の説明がとある通り、dl, dt, dd の読み上げ方法は定まっておらず、意図した読み上げになりません。

スクリーンリーダーによって、<dl> コンテンツの合計数、用語/定義のコンテキスト、ナビゲーション方法の公開方法は様々です。これらの違いは、必ずしもバグではありません。
https://developer.mozilla.org/ja/docs/Web/HTML/Element/dl#アクセシビリティの考慮

改善の見込みは…

本当は WAI-ARIA に associationlist という role が実装され、読み上げが改善されそうな見込みがありました(過去形)。

https://zenn.dev/yusukehirao/articles/dl-dt-dd-wai-aria

というのも、一時はこの仕様が WAI-ARIA 1.3(草案)にあったのですが、現在は削除されたようです。
経緯まで把握できていないのでなんとも言えませんが、改善されるとしてもかなり先のことになりそうです…。

https://x.com/cloud10designs/status/1749000327930810602

ul は正しい項目数が読み上げられる

ul で作成した例を読み上げると以下のような結果になります。

リスト  3項目
見出し    レベル3  図鑑ナンバー
0143
見出し    レベル3  タイプ
ノーマル
見出し    レベル3  説明文
1にちに たべものを 400キロ たべないと きが すまない。たべおわると ねむってしまう。
リストの外
  • ✅「リスト」と読み上げられ、並列の項目が並んでいることがわかる
  • 3 項目と読み上げられる
  • ✅「見出し」と読み上げられるので、見出しと値が区別される

ul は、 li の数を読み上げるため、「3 項目」と正しく読み上げられます。
また、見出しとしてマークアップしたことで、各項目の役割がわかりやすくなります。

table は正しい項目数(行数)が読み上げられる

table で作成した例を読み上げると以下のような結果になります。

テーブル  3行2列のテーブル  カビゴンの情報
1行  1列  図鑑ナンバー
2列  0143
2行  1列  タイプ
2列  ノーマル
3行  1列  説明文
2列  1にちに たべものを 400キロ たべないと きが すまない。たべおわると ねむってしまう。
テーブルの外
  • ✅「テーブル」と読み上げられ、構造を持った表であることがわかる
  • 3 行と読み上げられる
  • ✅ 列数によって、見出しと値が区別できる

📢 というわけで、第 1 試合スクリーンリーダーでは、ultable に軍配が上がりました🏆

第2試合 - 構造の機械可読性(≒SEO上のメリット)

ultable の違いを見ていきます。
今回のケースでは、「図鑑ナンバー=0143」「タイプ=ノーマル」のように、名前と値が 1 対 1 で対応しているため、この構造が伝わるとベストです。

ul の中で利用した hnp はあくまで階層構造

ul の実装例では、見出しに h3 要素、値に p 要素を使って構造を表しました。
しかし、h3 要素はあくまで階層構造を示すものであり、見出しと値の 1 対 1 関係を示すものではありません。
今回の実装例では p を 1 つしか使わないことでデータを表しましたが、本来は p が複数出現してもいい、ということをイメージすると、このことがわかりやすいと思います。

table は見出しと値の関係を機械的に認識できる

テーブルでは「列」という概念がありますから、見出しと値の関係を機械的に認識できます。

これは単なる思想の話ではなく、実用上のメリットが生まれることがあります。
それは検索エンジンで読み取られる際のことです。

例えば、table で実装されているページの例として、大塚製薬の会社概要を Bing で検索してみると、メタデータとして解釈されて検索ページ上に表示されていました。

Bingの検索結果ページのスクリーンショット。

大塚製薬の会社概要ページのスクリーンショット。右側にDevToolsで表jしいたHTMLを並べている。HTMLではtableで実装されている

また、(今回議論の対象としている表とは異なりますが、)より複雑な表の場合はよりリッチな表示(強調スニペット)として表示されることがあります。(例:HTMLタグ/HTML要素一覧 - TAG index


上記は Bing での例を紹介しましたが、以前までは Google 検索でも同様に強調スニペットとして、テーブルの内容が表示されることがありました。
しかし、記事執筆時点の 2024 年 4 月 6 日現在は確認できなくなっていました。この変更が一時的なものなのか恒久的なものなのかはわかりません(もし詳細知っている方いればコメントで教えてください)。
そのため効果は低減してしまっているのですが、Bing では現役なので考慮しておく価値はあると思います。

参考までに、以前の Google がテーブルを強調スニペットとして表示していた様子を載せている記事を以下に紹介します。

https://qiita.com/kingpanda/items/5e67be75350242320532

https://dlab-inc.jp/blog/featured-snippets-displaymethod/#5_2

📢 というわけで、第 2 試合構造の機械可読性では、table が勝利しました🏆

エキシビションマッチ - 拡張性

もしも、それぞれの列のラベルをつけるために見出し行を追加したくなったり、さらに情報を説明するために列を追加したくなった場合を考えます。
(想定している表とは異なる仮定を追加してしまうので、エキシビションマッチとしてます。)

dlul は見出し行や追加の列を追加できない

dlul (hnp)には、見出し行のような構造を作ることはできません。

追加の列に関しては、それぞれ追加の ddp を追加することはできますが、機械可読性の項目でも述べた通り、列としての構造にはなりません。

table は見出し行や追加の列を追加できる

table 要素は、thead, tfoot 要素を使って、見出し行や集計行を追加できます。
そのため、「項目」「内容」のような見出しを必要に応じて追加できます。

  <table>
    <caption>カビゴンの情報</caption>
+   <thead>
+     <tr>
+       <th>項目</th>
+       <th>内容</th>
+     </tr>
+   </thead>
    <tbody>
      <tr>
        <th>図鑑ナンバー</th>
        <td>0143</td>
      </tr>
      <tr>
        <!-- 略 -->
      </tr>
      <tr>
        <!-- 略 -->
      </tr>
    </tbody>
  </table>

さらに列を追加することもできます。

  <table>
    <caption>カビゴンの情報</caption>
    <thead>
      <tr>
        <th>項目</th>
        <th>内容</th>
+       <th>備考</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <th>図鑑ナンバー</th>
        <td>0143</td>
+       <td>ポケモンの数が1000を超えたため、4桁で表記されるようになりました</td>
      </tr>
      <tr>
        <!-- 略 -->
      </tr>
      <tr>
        <!-- 略 -->
      </tr>
    </tbody>
  </table>

📢 拡張性の観点でも、table にはメリットがありました🥇

使用事例を見てみる

実際に使用されている例を見てみます。ある程度マークアップが信頼できるサイトから、今回の対象である名前と値が 1 対 1 で対応しているメタデータを示す表を探しました。
今回確認した 3 例はどれも table で実装されていました。

MDN

MDN の HTML 要素のページにある、その要素の基本情報を示す「技術的概要」の表は table でした。

https://developer.mozilla.org/ja/docs/Web/HTML/Element/th#技術的概要

W3C

WAI-ARIA の仕様において、role の情報を説明する "Characteristics" の表は tableでした。
また、この例では "Characteristics", "Value" という見出し行があります。

https://w3c.github.io/aria/#list

Wikipedia

Wikipedia の基本情報の表は table でした。

https://ja.wikipedia.org/wiki/HyperText_Markup_Language

まとめ

名前と値が 1 対 1 で対応している情報を示す表について、dl ul table のそれぞれの実装方法を比較しました。

  • スクリーンリーダーでの読み上げ
  • 情報構造の機械可読性(SEO 上のメリット)

から table が優位 であるという結論に至りました。
実装方法に迷った際には参考にしてください。

GitHubで編集を提案

Discussion