# 表題がキャプションになっていない表

Source: https://easeweb.dev/learn/table-captions
Topics: 表とデータ > 表にキャプションがある (WCAG 1.3.1)
The fix: 各表と一緒に表示された表題を、その表の名前にします。表題を表の最初の子要素のcaption要素に移し、元の見た目に合わせてスタイルを付けるか、表題はそのままidを付け、表のaria-labelledbyでそれを指します。表題を表のセルの行として描かないでください。
Test with: screen reader, keyboard, zoom
References: https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html https://www.w3.org/WAI/WCAG22/Techniques/html/H39 https://www.w3.org/WAI/tutorials/tables/caption-summary/ https://www.w3.org/TR/html-aam-1.0/#table-element-accessible-name-computation https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption

よくある問題を再現した学習用のサンプルです。実在のクライアントの診断結果ではありません。コードは出発点となる実装例のため、実際の製品でも確認してください。ここでは支援技術でのテスト結果は報告していません。

## 場面

ネットショップの配送料金のページに、表が二つあります。一つは太字の表題「通常配送」の下に、もう一つは「速達」の下にあります。どちらも「重さ」と「料金」の同じ列を持ち、違うのは料金だけです。目で見る人は、表題を見ればどちらの表か分かります。スクリーンリーダー利用者がNVDAやJAWSのTキーで次の表へ移動すると、一つ目の表で「表、2列、3行」などと聞こえ、二つ目でも同じように聞こえます。「$9」が通常配送と速達のどちらの料金かを知るには、表を出て、ページを上の行まで読み戻す必要があります。

## 確認できる問題

表題は見た目では表に属していますが、コードでは表の前にたまたま置かれた段落です。二つを結び付けるものがないため、表にはアクセシブルな名前がありません。表題を表の中に入れ、すべての列にまたがる一つのセルの最初の行にするサイトもあります。これも同じです。表には名前がなく、表題はデータのセルとして読まれ、行が一つ多く数えられます。WCAG 1.3.1「情報及び関係性」は、画面で示した関係をコードでも伝えることを求めています。表の表題には、HTMLでは `caption` 要素を使います。WCAGの達成方法H39がまさにこの用途に挙げている要素です。`aria-labelledby` でも、ARIAで同じ結び付けができます。

表のすぐ上の本物の見出しは、より軽いケースです。見出しはページの見出し構造に含まれ、ページを順に読む人には表の直前に聞こえます。それでも表ごとの移動では飛ばされるため、表とも結び付けてください。

## 利用者への影響

ページが長いほど影響は大きくなります。表が二つの配送ページなら、少し読み戻すだけで済みます。成績表、曜日ごとに分かれた時刻表、地域ごとに表がある料金ページでは、目的の表を探すために一つずつ中を読む必要があり、推測で選ぶと別の表の数字を読み取ってしまいます。

## 自動チェックとAIの限界

表の上の段落は、どの自動チェックも合格にします。段落が表題だと示すものがないためです。axeには、表全体にまたがる一つのセルの最初の行を見つける `table-fake-caption` というルールがありますが、試験的なルールのため通常の実行には含まれず、この診断でも人が確認する候補として報告します。段落の場合も、この診断は確認候補を出します。名前のないデータ表について、すぐ上にある短い行を表題の候補として引用します。表の上の行がその表の表題か、各表の名前で表を区別できるかは、人が見て判断する必要があります。AIによるコード生成では、デザインどおりに表題を見出しやスタイルを付けた `div` として表の上に書き、`caption` を付けないことがよくあります。

## 修正前：表の上の段落にある表題

```html
<p class="table-title">Express delivery</p>
<table class="rates">
  <thead>
    <tr>
      <th scope="col">Weight</th>
      <th scope="col">Price</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Up to 1 kg</th>
      <td>$9</td>
    </tr>
    <tr>
      <th scope="row">1 to 5 kg</th>
      <td>$15</td>
    </tr>
  </tbody>
</table>
```

見出しのセルは正しいものの、表題は別の段落のため、表には名前がありません。表へ移動すると、スクリーンリーダーは「表、2列、3行」と読むだけで、上の通常配送の表と区別できる情報はありません。

## 修正前：表の最初の行にある表題

```html
<table class="rates">
  <thead>
    <tr>
      <td colspan="2" class="table-title">Express delivery</td>
    </tr>
    <tr>
      <th scope="col">Weight</th>
      <th scope="col">Price</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Up to 1 kg</th>
      <td>$9</td>
    </tr>
    <tr>
      <th scope="row">1 to 5 kg</th>
      <td>$15</td>
    </tr>
  </tbody>
</table>
```

表題は表の中に入りましたが、データのセルとしてです。表にはまだ名前がないため、スクリーンリーダーは表題を行に数えて「表、2列、4行」と読み、「Express delivery」はその最初のセルに着いたときにデータとして読むだけです。

## 修正後：caption

```html
<table class="rates">
  <caption>Express delivery</caption>
  <thead>
    <tr>
      <th scope="col">Weight</th>
      <th scope="col">Price</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Up to 1 kg</th>
      <td>$9</td>
    </tr>
    <tr>
      <th scope="row">1 to 5 kg</th>
      <td>$15</td>
    </tr>
  </tbody>
</table>
```

```css
.rates caption {
  text-align: start;
  font-weight: bold;
  padding-block-end: 0.5rem;
}
```

表題を表の最初の子要素の `caption` にしました。captionが表の名前になります。スクリーンリーダーは表に着くと「Express delivery、表、2列、3行」と読み、表の一覧にもこの名前で表示します。ブラウザーはcaptionを既定で中央にそろえるため、一つのルールで行頭にそろえ、太字の表題をデザインどおりに保ちます。ページの見た目は修正前と変わりません。段落や表題の行は不要になります。

## 修正後：表題を指すaria-labelledby

```html
<p id="express-title" class="table-title">Express delivery</p>
<table class="rates" aria-labelledby="express-title">
  <thead>
    <tr>
      <th scope="col">Weight</th>
      <th scope="col">Price</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Up to 1 kg</th>
      <td>$9</td>
    </tr>
    <tr>
      <th scope="row">1 to 5 kg</th>
      <td>$15</td>
    </tr>
  </tbody>
</table>
```

表題は元の場所のままidを付け、表はその表題を名前にします。スクリーンリーダーは同じく「Express delivery、表、2列、3行」と読みます。ボタンや絞り込みも並ぶカードのヘッダーや、ページの見出し構造に必要な見出しなど、表題を表の中に移せない場合に使います。idはページ内で重複させず、コンポーネントを使い回しても表題を指し続けるようにします。表の中に表題の行がある場合は、先に表の上へ出してください。

## どの方法を選ぶか

どちらも見えている表題を表の名前にし、1.3.1を満たします。`caption` を使ってください。HTMLだけで同じ役割を果たし、重複しないように合わせるidも要らず、表題が表の中にあるため、表と一緒に移動し、隠れ、印刷されます。`aria-labelledby` は、操作部品を含むカードのヘッダーなど、表題を表の外に置く必要がある場合に使います。

## 実装の判断

修正のほかに、判断が必要な場面があります。

- captionは一つで最初に：`caption` は表の最初の子要素にし、一つの表に一つだけです。「料金は税込みです」のような補足は、同じcaptionの中で行を分けて書くか、`aria-describedby` で表と結び付けた段落に書きます。
- 表題を見出しにもする場合：captionの中に `<caption><h3>Express delivery</h3></caption>` のように見出しを入れられます。表題が見出し構造に入り、表の名前にもなります。頼る前に、使うスクリーンリーダーでの読まれ方を確認してください。
- `figure` の中の表：表が `figure` の唯一の内容なら、HTMLでは `caption` ではなく `figcaption` を使うよう定めています。`figcaption` は表の名前にならないため、表の `aria-labelledby` でも `figcaption` を指してください。そうしないと、表ごとの移動では読まれません。
- 画面に表題がない場合：1.3.1は、デザインにない名前までは求めていません。ただ、表が複数あるページでは名前があるほうが使いやすくなります。デザインが許せば見えるcaptionを付け、難しければ `aria-label` を付けます。
- 古い `summary` 属性は廃止されています。説明はcaptionに入れるか、`aria-describedby` で結び付けます。
- レイアウト用の表：配置のためだけの表にはcaptionを付けません。`role="presentation"` を付けるか、CSSのレイアウトに置き換えます。

表の中の見出しのセルは別の問題で、[見出しが太字だけのデータ表](/ja/learn/table-headers)で扱っています。どちらも同じ表のコンポーネントで対応してください。

## 修正の確認

1. コードで、上や中に表題が表示されたデータ表をすべて探し、表題がその表の `caption` か、表の `aria-labelledby` が指す要素になっているか確認する。
2. 最初の行がすべての列にまたがる一つのセルだけの表がないこと、各表のcaptionが一つだけで最初の子要素であることを確認する。
3. スクリーンリーダーで表から表へ移動し（NVDAとJAWSではT）、各表が表題と一緒に読まれるか確認する。
4. VoiceOverのローターなど、スクリーンリーダーの表の一覧を開き、各表の名前でほかの表と区別できるか確認する。
5. 修正前の見た目と比べ、表題が同じ場所に同じスタイルで表示されているか、200%の拡大表示や狭い画面でも確認する。

OS、ブラウザーと支援技術のバージョン、ビルド、実施日、各手順の実際の結果を記録してください。表のコンポーネントを変更した際は、再度確認します。

## 制限

このサンプルは、どの表でも同じ結果になることを保証するものではありません。captionをいつ読むか、表の大きさをどう伝えるかはスクリーンリーダーによって異なり、表の一覧がないものもあります。名前は表の中身を表していなければ役に立ちません。「料金」というcaptionの表が二つあれば、captionがない場合と同じく区別できません。

---

このガイドをコーディングエージェントで使う: MCP https://easeweb.dev/mcp（設定は https://easeweb.dev/ja/services/developers）
