TL;DR
<button>、<nav>などのネイティブHTML要素で要件を満たせる場合は、ARIAを使わない- ARIAはHTMLに存在しないウィジェット(タブパネル、ツリービュー)や動的通知(
aria-live)でのみ補完的に使用する - 誤ったARIA実装は、ARIAを使わない場合よりもアクセシビリティを損なう(WebAIM 2021年調査では41%エラー増)
WAI-ARIAの制約と危険性
WAI-ARIAとは
WAI-ARIA(以降ARIA)は、動的コンテンツと高度なUIコントロールをアクセシブルにする技術仕様である1。HTMLに存在しないタブパネル、ツリービュー、カレンダー、ライブリージョン(動的に更新される領域)などに対応する。
「No ARIA is better than Bad ARIA」の原則
W3C ARIA Authoring Practices Guideでは、「No ARIA is better than Bad ARIA」という原則を示している2。誤ったロール指定や状態管理の不備など、仕様に反したARIAの使用は、ARIAを使わない場合よりもアクセシビリティを損なう。
WebAIMの2021年の調査によれば、ARIAが存在するページは、存在しないページに比べて検出されたエラー数が平均して41%多かった3。開発者の善意が却って品質を低下させる皮肉な結果である。
ARIAの最大の危険性は、スクリーンリーダーユーザーにのみ異なる体験を提供できる点である。視覚的には正常なUIが、スクリーンリーダーでは全く異なる情報を伝える「代替現実」(スクリーンリーダー利用者にだけ異なる体験が提示される現象)を作り出す4。開発者がスクリーンリーダーを日常的に使用していない場合、この種の誤りをリリース前に検出できない。
アクセシビリティ・ツリーとARIA
ARIAはセマンティクス(意味情報)のみを支援技術に提供する技術である5。キーボード操作、フォーカス管理、視覚的スタイルは別途実装する必要がある。
ブラウザはDOMから「アクセシビリティ・ツリー」を生成し、スクリーンリーダーに情報を提供する。ARIAはこのツリーの情報を上書きするが、DOM自体の振る舞いは一切変更しない67。
このアクセシビリティ・ツリーには、各要素の以下の4つのプロパティが含まれる。
- Role(役割):要素が何であるか(例:ボタン、リンク、見出し)
- Name(名前):要素のラベルや識別子(例:「送信」、「メニュー」)
- State(状態):現在の状況(例:チェックされている、無効化されている、展開されている)
- Value(値):要素が保持する値(例:スライダーの現在値)
ARIAロールの実装責任
ARIAロールを適用する場合、開発者はそのロールに対応するすべての機能を実装する責任がある8。
- キーボード操作: ロールに期待される操作(例:
role="button"なEnter/Spaceキー) - フォーカス管理:
tabindex属性、複雑なウィジェットではaria-activedescendant(コンテナ内のアクティブな子要素を指定) - ARIA状態の管理:
aria-pressed、aria-expanded、aria-selectedなどの動的更新 - 視覚的フィードバック: CSSによるフォーカス表示、状態変化の視覚表現
セマンティクスの上書き
ARIAは既存のHTMLセマンティクスを上書きする9。ブラウザはHTML要素を解釈後、ARIAロールで上書きした情報をアクセシビリティツリーとしてスクリーンリーダーに送信する。
例えば、<table role="log">と記述すると、支援技術はそれをテーブルではなくログとして認識する。結果、テーブル固有の機能(行・列のナビゲーション、ヘッダ情報の読み上げなど)が失われる。
この上書きは視覚的レンダリングに影響しない。視覚的にはテーブルのまま表示されるが、スクリーンリーダーユーザーにとってはテーブルではなくなる。
セマンティックHTMLとの使い分け
セマンティックHTMLを優先する
W3Cは「ARIAの第一原則」として、次のように定めている10。
要求するセマンティクスと振る舞いを組み込んだネイティブHTML要素が使用できるならば、要素を再利用してARIAを追加するのではなく、そのネイティブ要素を使用すべきである。
この原則に従わない場合、キーボード操作・フォーカス管理・状態管理のすべてを開発者が実装する責任を負う。
ネイティブHTML要素は「HTML Accessibility API Mappings(HTML-AAM)」により、ブラウザが自動的にアクセシビリティ情報を生成する11。この仕組みにより、開発者はアクセシビリティ対応のメンテナンスコストをブラウザベンダーに委託できる。
ARIAを使用すべき場面
1. カスタムウィジェット(タブパネル)
HTMLにはタブUIのネイティブ要素が存在しないため、ARIAが必要である。APG(ARIA Authoring Practices Guide)では<button>要素にrole="tab"を適用する実装を推奨している。これはネイティブの<button>が持つキーボード操作とフォーカス管理の機能を活用しつつ、タブとしてのセマンティクスを支援技術に伝える正しいパターンである12。
<div role="tablist" aria-label="プロフィール設定" aria-orientation="horizontal">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">基本情報</button>
<button role="tab" aria-selected="false" tabindex="-1" aria-controls="panel-2" id="tab-2">セキュリティ</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
<!-- 基本情報の内容 -->
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
<!-- セキュリティの内容 -->
</div>
role="tab"とaria-selectedで現在のタブを示し、aria-controlsとaria-labelledbyでタブとパネルの関連を明示する。ただしaria-controlsは、必須ではない。JAWSのみが対応していて、NVDAやVoiceOverはほぼ無視されるようだ。そのためaria-labelledbyによる双方向の関連付けを併用する必要がある。さらに、左右矢印キーでのタブ切り替え、Home/Endキーでの最初/最後のタブへの移動、Tabキーでのフォーカス移動などをJavaScriptで実装する必要がある12。
キーボード操作には、ロービングタブインデックス(複数要素間でフォーカスを動的に管理するパターン)を採用する。選択タブにはtabindex属性を設定せず(<button>はネイティブにフォーカス可能)、非選択タブはtabindex="-1"を維持する(APG準拠)。これにより、Tabキー1回でタブリスト全体を通過し、矢印キーで個別のタブを選択する操作性を実現する。
2. 動的コンテンツの通知(aria-live)
ページの一部が動的に更新される場合、aria-liveでスクリーンリーダーに通知する。
<div aria-live="polite" aria-atomic="true" class="status-message">
<!-- JavaScriptで動的に更新 -->
</div>
<script>
// 保存完了後
document.querySelector('.status-message').textContent = '変更を保存しました';
</script>
aria-live="polite"は、スクリーンリーダーが現在の読み上げを終えてから通知する。aria-atomic="true"は、領域全体を読み上げることを指示する。緊急の通知にはaria-live="assertive"を使用する13。
3. 要素間の関係の明示(aria-describedby)
フォーム要素とヘルプテキストの関連を明示する場合に使用する。
<label for="password">パスワード</label>
<input type="password" id="password" aria-describedby="password-help password-requirements" />
<div id="password-help">アカウントを保護するため、強力なパスワードを設定してください。</div>
<div id="password-requirements">8文字以上、英数字と記号を含む必要があります。</div>
aria-describedbyにより、スクリーンリーダーはフォーカス時にヘルプテキストを読み上げる14。
実装時の注意点
よくある実装ミス
ARIAの実装で頻繁に発生する誤りには以下がある15。
状態属性の更新忘れ: ユーザーがメニューを展開した際にaria-expanded="false"からaria-expanded="true"への更新を忘れる。スクリーンリーダーは展開されていないと認識し続ける。
aria-activedescendantの管理ミス: リストボックスやメニューでアクティブ項目が変わった際、aria-activedescendantの設定や消去を忘れる。スクリーンリーダーが現在の選択項目を正しく読み上げられない。
キーボードイベントの不完全な処理: カスタムウィジェットで矢印キーを処理する際、event.preventDefault()を呼び忘れる。結果として、矢印キーでページがスクロールしてしまう。
ロールの誤用: コンテキストに合わないロールを使用する。例えば、<select>の選択肢にrole="option"を使うべき場所でrole="menuitem"を使用し、スクリーンリーダーが混乱する。
冗長なARIA属性: <button role="button">のように、ネイティブ要素に不要なロールを重ねて記述する。ブラウザに混乱を与え、予期しない動作の原因となる。
これらの誤りは、視覚的には問題なく動作しているように見えるため、スクリーンリーダーでのテストなしには発見できない。
テストの必須性
ARIAを使用したウィジェットは、実際のスクリーンリーダーユーザーによるテストなしにリリースしてはならない16。視覚的には正常でも、スクリーンリーダーでは使用不可能な場合がある。
推奨テスト手順
キーボードのみでのテスト: スクリーンリーダーを使わず、マウスなしで操作できるか確認する。キーボードで操作できない場合、スクリーンリーダーでも使用できない。
複数の支援技術でのテスト: ブラウザとスクリーンリーダーの組み合わせで予期しない動作が発生する。以下の環境を推奨する。
- Windows: NVDA + Firefox、JAWS + Chrome
- macOS: VoiceOver + Safari
- モバイル: iOS VoiceOver、Android TalkBack
実際のユーザーによるテスト: 開発者のテストでは発見できない問題を検出する。可能であれば、実際のスクリーンリーダーユーザーにレビューを依頼する。