スクリーンリーダー対応では、見た目だけでなく、要素の意味、読み上げる名前、状態、操作順をコードで伝える必要があります。
この記事では、ネイティブHTMLを土台に、ARIAで補う場面と、NVDA、VoiceOver、TalkBackで確認する手順を実装例とともに整理します。
スクリーンリーダーが受け取る情報
スクリーンリーダーは、画面上の情報を音声や点字で伝え、見出し、リンク、フォームなどの単位で移動や操作を支援する技術です。
ブラウザはHTMLの要素や属性からアクセシビリティツリーと呼ばれる情報構造を組み立て、スクリーンリーダーはその情報を利用します。
実装で特に確認したいのは、次の三つです。
- 名前、役割、状態や値
- 「検索」「ボタン」「押されている」のように、その部品が何で、現在どうなっているかを伝える情報です。
- 意味のある構造
- 見出し、リスト、表、ナビゲーションなどの関係をHTMLで表したものです。
- フォーカス
- キーボードや支援技術で現在操作している位置です。操作できる部品へ到達でき、移動後の位置を把握できる必要があります。
同じHTMLでも、ブラウザ、OS、スクリーンリーダー、設定の組み合わせによって読み方や操作方法は変わることがあります。
一つの環境で期待どおりに読まれたことを、すべての環境での保証とは考えないことが大切です。
ネイティブHTMLを先に選ぶ
<button>には、ボタンとしての役割、キーボードからの到達、EnterキーやSpaceキーによる操作など、ブラウザが提供する基本動作があります。
一方、クリックイベントを付けただけの<div>には、その意味も操作方法も備わりません。
<!-- 推奨:意味と基本操作をブラウザが提供する -->
<button type="button">メニューを開く</button>
<!-- 避ける:見た目が同じでもボタンとして伝わらない -->
<div onclick="openMenu()">メニューを開く</div>
ARIAは、HTMLだけでは表せない役割、名前、状態、要素間の関係を補う属性群です。
ARIAを付けても、フォーカス移動や矢印キー操作は自動では追加されません。
カスタムUIでは、ARIAとキーボード動作を一組として設計します。
詳しい属性の選び方は、ARIAの役割とアンチパターンの解説も参照してください。
aria-hidden='true'は、指定した要素とその子孫を支援技術から隠します。
フォーカスできるボタンやリンク、その説明文を含む親要素には付けないでください。
見えて操作できるのに読み上げから消えると、利用者は部品の存在や目的を把握できません。
ページ構造と移動順をそろえる
見出しとランドマーク
見出しは、文字サイズを変えるためではなく、内容の階層を示すために使います。
WordPressの投稿タイトルをページのh1とし、本文はh2、その下位項目はh3の順にします。
header、nav、main、aside、footerは、ページの主要領域を示します。
同じ種類のnavが複数ある場合は、「主要ナビゲーション」「パンくずリスト」のように、aria-labelまたはaria-labelledbyで区別します。
DOM順とフォーカス順
CSSだけで要素の見た目の順番を入れ替えると、画面の位置、読み上げる順番、Tabキーの移動順が食い違うことがあります。
まずDOMを理解しやすい順に並べ、その順序を崩さないレイアウトを選びます。
正の値を持つtabindexで順番を作り直す方法は、変更時に破綻しやすいため避けます。
スキップリンク
繰り返されるナビゲーションを飛ばして本文へ移動できるリンクを、ページの先頭付近に置きます。
キーボードでフォーカスしたときに見えることと、リンク先のmainへ実際に移動できることを確認します。
<a class="skip-link" href="#main-content">本文へ移動</a>
<main id="main-content" tabindex="-1">
...本文...
</main>
画像とアイコンの代替テキスト
altは画像の外見を機械的に説明する欄ではありません。
その画像が文脈で担う目的を、画像が見えない場合にも伝えるための短い代替です。
| 画像の種類 | 伝え方 | 例 |
|---|---|---|
| 情報を伝える画像 | 本文の理解に必要な内容をaltで簡潔に伝える |
グラフの結論を本文でも説明する |
| リンクやボタンの画像 | 操作後に何が起きるかを部品の名前として伝える | 「検索」「次の画像」のように目的を示す |
| 装飾画像 | alt=''として読み上げ対象から外す |
区切り線や意味を持たない背景装飾 |
<!-- 情報画像 -->
<img src="venue.jpg" alt="登壇者が満席の会場で講演している">
<!-- アイコンは装飾、操作名はボタンの可視テキストで伝える -->
<button type="submit">
<svg aria-hidden="true" focusable="false">...</svg>
検索
</button>
<!-- 装飾画像 -->
<img src="divider.png" alt="">
同じ画像でも、掲載場所によって目的は変わります。
本文ですでに同じ情報を説明している場合は、altで長く繰り返さないほうが理解しやすいこともあります。
フォームのラベル、説明、エラー
入力欄には、見た目で近くに文字を置くだけでなく、<label>を関連付けます。
プレースホルダーは入力後に消えるため、ラベルの代わりにはなりません。
<label for="email">メールアドレス(必須)</label>
<p id="email-hint">返信を受け取れるアドレスを入力してください。</p>
<input id="email" name="email" type="email"
autocomplete="email" required
aria-describedby="email-hint">
エラー時は、色だけに頼らず、「メールアドレスを入力してください」のように問題と直し方を文章で示します。
該当欄へaria-invalid='true'を付け、エラー文をaria-describedbyで関連付ける方法があります。
送信後に複数のエラーが出る場合は、先頭にエラー一覧を置いてフォーカスを移すなど、利用者が修正を始められる位置も設計します。
フォーム全体の設計例は、フォームのエラー対策と検証手順で詳しく解説しています。
ラジオボタンやチェックボックスの選択肢にはfieldsetとlegendを使い、何について選ぶのかをまとめて伝えます。
<fieldset>
<legend>希望する連絡方法</legend>
<label><input type="radio" name="contact" value="email">メール</label>
<label><input type="radio" name="contact" value="phone">電話</label>
</fieldset>
表は見出しとの関係を伝える
データ表には内容を表すcaptionを付け、列見出しにはscope='col'、行見出しにはscope='row'を使います。
セルを移動したときに、どの見出しに属する値かを追える構造にするためです。
<table>
<caption>サポート窓口の受付時間</caption>
<thead>
<tr>
<th scope="col">曜日</th>
<th scope="col">受付時間</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">平日</th>
<td>9時から17時</td>
</tr>
</tbody>
</table>
複雑な表にheadersとidを多数追加する前に、表を目的別に分けられないか検討します。
表の設計とレスポンシブ対応は、データ表のアクセシビリティ実務ガイドも参考になります。
ダイアログとタブのフォーカス管理
モーダルダイアログ
モーダルダイアログでは、開いたことを伝えるだけでは足りません。
開いた直後に目的に合う要素へフォーカスを移し、TabキーとShift+Tabキーの移動をダイアログ内に保ち、閉じた後は原則として起点のボタンへ戻します。
Escapeキーで閉じられることと、背景を操作できないことも確認します。
<button id="open-terms" type="button">利用規約を読む</button>
<div role="dialog" aria-modal="true"
aria-labelledby="terms-title" hidden>
<h2 id="terms-title" tabindex="-1">利用規約</h2>
<p>規約の本文</p>
<button type="button">閉じる</button>
</div>
role='dialog'やaria-modal='true'は、開閉、背景の無効化、フォーカスの移動を実行しません。
これらの動作は別途実装し、実際の操作で確かめます。
長い本文やリストを含む場合は、説明全体をaria-describedbyで一度に読ませず、見出しから順に読み進められる構造を保ちます。
詳しい判断例は、アクセシブルなモーダルダイアログの設計で確認できます。
タブ
タブでは、選択中のタブだけを通常のTab移動先にし、同じタブ列の中は矢印キーで移動できるようにします。
aria-selected、tabindex、表示するパネルを、選択変更のたびに同期させます。
<div role="tablist" aria-label="設定">
<button role="tab" id="tab-general"
aria-controls="panel-general"
aria-selected="true" tabindex="0">一般</button>
<button role="tab" id="tab-detail"
aria-controls="panel-detail"
aria-selected="false" tabindex="-1">詳細</button>
</div>
<section role="tabpanel" id="panel-general"
aria-labelledby="tab-general">...</section>
<section role="tabpanel" id="panel-detail"
aria-labelledby="tab-detail" hidden>...</section>
このHTMLは関係を表す骨組みです。
左右矢印、Home、End、必要に応じたEnterまたはSpaceの動作は、表示の切り替えとともに実装する必要があります。
単純な開閉だけなら、タブを作る前に<details>と<summary>で要件を満たせるか検討します。
動的な更新を必要なときだけ知らせる
画面の一部だけが更新されると、フォーカスを動かさずに内容が変わるため、スクリーンリーダーの利用者が更新に気付けないことがあります。
保存完了や検索結果件数のような状態通知にはrole='status'、直ちに注意が必要な短いエラーにはrole='alert'を使う方法があります。
<div id="save-status" role="status" aria-atomic="true"></div>
通知領域は更新前からDOMに置き、必要な時点でテキストを追加します。
同じ通知を繰り返したり、入力のたびに長文を読み上げたりすると操作を妨げるため、伝える内容と頻度を絞ります。
言語指定と視覚的に隠す補足
日本語ページには<html lang='ja'>を指定し、文中の英語部分だけ発音を切り替えたい場合は、その範囲へlang='en'を付けます。
略語は、初出時に正式名称や意味を本文で説明すると、視覚でも音声でも理解しやすくなります。
画面には表示せず、スクリーンリーダーにだけ補足を渡す場合は、要素を小さく切り抜いて画面外に見せる専用クラスを使います。
display:noneやvisibility:hiddenを指定した内容は、通常はスクリーンリーダーからも利用できません。
.sr-only {
position: absolute !important;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
可視ラベルを置ける場合は、専用テキストで隠すより、画面上にも意味が分かるラベルを表示する方法を優先します。
NVDA、VoiceOver、TalkBackでの確認
スクリーンリーダーには、文書を読み進める操作と、フォーム部品を直接操作する状態があります。
名称や切り替え方は製品や設定で異なるため、ショートカットを暗記するより、見出し、リンク、フォーム、ランドマークの各一覧から目的の場所へ移動できるかを確認します。
| 環境 | よく使う移動 | 確認すること |
|---|---|---|
| NVDAとWindows | Hで次の見出し、Shift+Hで前の見出し。Kでリンク、Fでフォーム部品へ移動 |
閲覧モードで構造をたどれ、フォーカスモードで部品を操作できるか |
| VoiceOverとmacOS | VOキーと矢印キーで移動し、ローターから見出しやリンクを選ぶ | 読み上げ順とキーボードフォーカスが画面の順序と一致するか |
| VoiceOverとiOS | 左右フリックとローターで移動 | 見出し、リンク、フォームが目的別に見つかるか |
| TalkBackとAndroid | 左右スワイプと読み上げコントロールで移動 | 要素名、役割、状態が伝わり、ダブルタップで操作できるか |
キー割り当てやジェスチャーは、OS、キーボード配列、アプリ、利用者設定によって変わることがあります。
テスト開始前に、使用する環境の操作ヘルプで現在の割り当てを確認してください。
主要シナリオのチェックリスト
- ページタイトルと最初の見出しから、ページの目的を把握できる。
- 見出し一覧だけでも内容の階層と順序を理解できる。
- スキップリンクから本文へ移動できる。
- すべてのリンクとボタンに、行き先または動作が分かる名前がある。
- Tabキーで操作部品へ順番に到達でき、フォーカス位置が視覚でも分かる。
- 情報画像には目的に合う代替テキストがあり、装飾画像は読み上げを増やさない。
- フォームのラベル、説明、必須状態、エラー、直し方が関連付けられている。
- ダイアログを開閉したときにフォーカスを見失わない。
- 表のセルを移動したとき、対応する行見出しと列見出しを把握できる。
- 状態通知が必要な回数だけ読まれ、作業を遮りすぎない。
主要な利用経路は、キーボードだけでも完了できるかを先に確認します。
その後でスクリーンリーダーを使うと、「到達できない問題」と「意味が伝わらない問題」を分けて調べやすくなります。
自動検査と手動検査を分担させる
HTML検証やアクセシビリティ検査ツールは、ラベルの欠落、重複ID、色の組み合わせなど、規則で判断できる問題を早く見つける助けになります。
ただし、ボタン名が利用者の目的に合うか、ダイアログを開いた後に迷わないか、読み上げ順が理解しやすいかは、実際の操作を伴う確認が必要です。
運用では、次の分担にすると確認漏れを減らせます。
- コンポーネント作成時に、名前、役割、状態、キーボード操作を仕様として残す。
- コードレビューと自動検査で、機械的に検出できる問題を早期に直す。
- 主要画面の操作シナリオを、キーボードとスクリーンリーダーで定期的に再確認する。
- 不具合を直したら、同じ部品を使う別画面も確認する。
この実装例とWCAG適合の関係
この記事で扱った内容は、非テキストコンテンツ、情報と関係性、キーボード操作、ブロックの回避、フォーカス順序、見出しとラベル、エラーの特定、名前、役割、状態などの達成基準と関係します。
ただし、掲載したスニペットを組み込むだけで、ページやサイト全体の適合を宣言できるわけではありません。
適合を評価する場合は、対象範囲と目標レベルを決め、該当する達成基準を通して試験し、結果を記録します。
現行版との違いを確認したい場合は、WCAG 2.2で追加された達成基準の解説を参照してください。
実装と確認の進め方
- ページの見出し、ランドマーク、DOM順を整える。
- リンク、ボタン、フォームをネイティブHTMLで実装する。
- 画像の目的を分類し、必要な代替テキストを付ける。
- カスタムUIに必要なARIAとキーボード動作を実装する。
- キーボードだけで主要タスクを完了できるか確認する。
- NVDA、VoiceOver、TalkBackのうち、対象利用環境に合う組み合わせで読み上げと操作を確かめる。
- 自動検査を継続し、変更による再発を見つける。
スクリーンリーダー対応の出発点は、特別な属性を増やすことではありません。
意味の合うHTMLを選び、画面に見えている名前と読み上げる名前をそろえ、操作の前後でフォーカスを見失わないようにします。
そのうえで実機確認を繰り返すと、コード上の正しさと利用時の分かりやすさを一緒に改善できます。
この記事に関連する株式会社greedenの取り組み
スクリーンリーダー対応は、正しいHTMLと実機確認を継続して初めて保てます。
株式会社greedenは、Webアクセシビリティサービス「UUU」とチェックサービスを通じ、サイトの確認と改善の第一歩を支援しています。
