APG と実装の違い
現場の実例
WAI-ARIA APG は実装の出発点ですが、絶対のルールではありません。主要な UI ライブラリは、実際のスクリーンリーダーでの検証結果を踏まえて、あえて APG の記述と異なる実装を選ぶことがあります。
このページでは、そうした「意図的な乖離」の事例を、APG の定め・現場の実装・その理由(経緯・実測結果)・採用しているライブラリ・出典とともに集めています。パターン名から詳細ページへ進めます。
モーダルダイアログ
ネイティブ <dialog> は「使うな」から「条件付きで使ってよい」へ転換した
- APG では
- APG の Dialog パターンは role="dialog" + JavaScript のカスタムウィジェットとして書かれており、ネイティブ <dialog> 要素の採否には直接踏み込んでいない。
- 現場では
- アクセシビリティ実務家の定番記事だった Scott O'Hara の "Having an open dialog"(2019)は長年「ネイティブ dialog は避けよ」の根拠だったが、2023 年 1 月に「条件付きで使ってよい」へ改訂された。一方 a11y-dialog のような定番ライブラリは、ブラウザ間のスタイル差や open イベントの不在などを理由に、今もネイティブ dialog を採用していない。
- なぜ
- 2019 年時点では Chromium のみの対応で、フォーカス配置の不備・スクリーンリーダー間の挙動不一致・閉じた後にフォーカスが呼び出し元へ戻らない、といった問題が実測されていた。2022 年に Safari 15.4 と Firefox 98 が対応して全主要ブラウザが実装したことで、著者は「利用者のブラウザ統計が十分に新しくなるまでは堅牢なカスタム実装を推奨」という条件付き容認へ立場を更新した。意見ではなく「主要ブラウザの実装状況」という計測可能な基準で推奨が変わった好例で、なお showModal() を使えば aria-modal 相当の挙動が自動で付く点も現場の判断材料になっている。
採用:Scott O'Hara、a11y-dialog
出典:Scott O'Hara — Having an open dialog(新しいタブで開きます)、a11y-dialog — On the dialog element(新しいタブで開きます)
フォーカストラップは keydown 監視ではなく「見えない番兵要素」方式に収束した
- APG では
- APG 本文は「Tab で次の要素へ、最後の要素では最初へ戻す」という挙動要件のみを定め、実装方式(番兵要素か keydown ハンドリングか)は開発者に委ねている。
- 現場では
- APG 自身の参照実装(dialog.js)はダイアログの直前・直後に tabindex="0" の div を動的に挿入し、そこへフォーカスが来たら端へ戻す方式。Radix UI(react-focus-guards)も文書端に隠し span を 2 つ挿入し、floating-ui の FloatingFocusManager も guards オプション(デフォルト有効)で同様の隠しガード要素を生成する。業界が独立に同じ方式へ収束している。
- なぜ
- Tab キーの keydown を傍受するだけの方式では、マウスクリックやスクリーンリーダーの仮想カーソル操作など「Tab 以外の手段」でフォーカスがダイアログ外へ抜けるケースを検知できない。フォーカスイベントベースの番兵方式なら移動手段を問わず逸脱を捕捉できる。これは APG との対立ではなく、APG が沈黙している実装詳細について参照実装と主要ライブラリが同じ結論に達した「収束」の実例で、フォーカストラップを自作するときに選ぶべき方式の答えでもある。
採用:W3C APG reference implementation、Radix UI、floating-ui
出典:w3c/aria-practices#545 — Documenting the sentinel technique(新しいタブで開きます)、W3C APG — Dialog (Modal) Pattern(新しいタブで開きます)、Floating UI — FloatingFocusManager(guards)(新しいタブで開きます)
アラートダイアログ
「初期フォーカスは安全側のボタンへ」はライブラリではなく利用者の責務
- APG では
- 破壊的な操作の確認ダイアログでは、初期フォーカスをキャンセルなど「安全側」のコントロールに置く。
- 現場では
- headless UI ライブラリ(Base UI・Radix UI・React Aria)の AlertDialog は、デフォルトでは「ダイアログ内の最初のフォーカス可能要素」へフォーカスし、どのボタンが安全側かの判定はしない。代わりに initialFocus などの prop を提供し、フォーカス先の方針は利用者に委ねる。
- なぜ
- どのボタンが「安全側」かは DOM 順・ラベル文言・アプリの文脈に依存し、ライブラリには判定できない。そのため headless ライブラリは機構(フォーカス移動・トラップ・復帰)だけを保証し、方針(どこへ置くか)を API として公開する分業が確立した。APG の要求を満たすのは利用側の実装で、initialFocus でキャンセルボタンを指定する、または安全側ボタンを DOM の先頭に置くのが定石になる。
採用:Base UI、Radix UI、React Aria
出典:Radix UI — Alert Dialog(新しいタブで開きます)、Base UI — Alert Dialog(新しいタブで開きます)
メニュー / メニューバー
サイトナビゲーションに menu / menubar ロールを使わない
- APG では
- menu / menubar パターンは、Enter / Space / 矢印キーなど独自のキーボード規約一式を実装した「アプリケーション的な」メニューウィジェットとして定義されている。
- 現場では
- 現場のコンセンサスは「サイトナビゲーションは <nav> + 入れ子の <ul><li><a> のリンクリストで作り、menu 系ロールを付けない」。ドロップダウン付きナビもボタン + aria-expanded の disclosure パターンで組む。
- なぜ
- menu ロールを付けるとスクリーンリーダーがアプリケーション操作モードに切り替わり、ページ閲覧には不要な独自キー操作をユーザーに強制する。しかも開発者がコピペで実装するため矢印キー処理などが不完全なまま公開され、ナビゲーション自体が操作不能になる事例が観察された。ARIA の第一原則「HTML で足りるなら ARIA を使うな」に従えば、リンクの入れ子リストで意味的に十分——というのが Adrian Roselli らの結論で、現在は主要な実務家の共通見解になっている。menu / menubar が適切なのは、Google ドキュメントのメニューバーのような本物のアプリケーションメニューだけ。
採用:Adrian Roselli、Heydon Pickering、Terrill Thompson
出典:Adrian Roselli — Don't Use ARIA Menu Roles for Site Nav(新しいタブで開きます)、Adrian Roselli — Link + Disclosure Widget Navigation(新しいタブで開きます)
コンボボックス
aria-selected はハイライト追随ではなく「確定した選択」だけに使う
- APG では
- APG のコンボボックス実装例では、矢印キーで候補をハイライトするたびに aria-activedescendant と同期させて、その option の aria-selected="true" を移動させる。
- 現場では
- React Aria や Base UI は、aria-selected を「確定済みの選択値」にのみ付ける。キーボード操作中のハイライトは aria-activedescendant(と視覚スタイル)だけで伝え、Enter などで確定した瞬間に初めて aria-selected="true" になる。
- なぜ
- 「フォーカス(いま指している候補)」と「選択(確定した値)」は別の概念で、ハイライトのたびに aria-selected を動かすと、スクリーンリーダーが移動のたびに「選択済み」と読み上げ、値が確定したとユーザーが誤解しうる。複数選択 UI では「選択済み」の意味が二重になり整合しない。activedescendant がフォーカス位置を伝える以上、selected は本来の意味(選択状態)に限定するのが現場の解釈になっている。
採用:React Aria、Base UI
出典:React Aria — useComboBox(新しいタブで開きます)、Base UI — Combobox(新しいタブで開きます)
モバイルでは role="combobox" を捨て、トレイ内の role="searchbox" に切り替える
- APG では
- 入力欄に role="combobox" を付け、aria-expanded / aria-controls でリストボックスと関連付け、矢印キーで候補間を移動することを主要な操作方法とする。
- 現場では
- React Aria の ComboBox はモバイルでは外側の入力を廃止してボタン化し、タップで開く全画面トレイの中に検索入力を置く。その入力のロールは combobox ではなく searchbox にしている。
- なぜ
- 実機テストで、トレイ内の入力に role="combobox" を残すとスクリーンリーダーが「ダブルタップで閉じる」という誤ったアナウンスをする不具合が判明した。また、タッチのスクリーンリーダー利用者は物理キーボードを持たず、APG が前提とする矢印キーでの候補移動ができない。外側とトレイ内で入力が二重になる状態管理の複雑さも重なり、「モバイルは別の UI として設計する」判断に至った。APG の型を守ることより、入力手段ごとの実際の操作性を優先した代表例。
採用:React Aria、React Spectrum
出典:React Aria Blog — Creating an accessible autocomplete experience(新しいタブで開きます)
listbox は「隣接要素」に置けない — ポータル + aria-hidden の動的制御で補う
- APG では
- APG の実装例では listbox は combobox の兄弟要素として DOM に置かれ、スクリーンリーダーが入力と候補リストの間を素直に行き来できることを前提にしている。
- 現場では
- React Aria は親要素の overflow: hidden / scroll によるクリッピングを避けるため listbox を document 末尾へポータルする。崩れた前提を補うため、TreeWalker で DOM 全体を走査して入力と listbox 以外へ aria-hidden を付与し、開いている間は MutationObserver で DOM の変化を監視し続ける。
- なぜ
- 実アプリでは combobox がスクロールコンテナやオーバーフロー制御の中に置かれるため、候補リストを APG どおり隣に置くと欠けて表示される。ポータルは視覚問題を解決するが、DOM 上の距離が生まれてスクリーンリーダーの読み上げ順が壊れる。そこで「入力と listbox 以外を一時的に支援技術から隠す」ことで、APG が想定していたスクリーンリーダー体験そのものを別の手段で再現した。教科書の DOM 構造ではなく、教科書が実現したかった体験を移植した実例。
採用:React Aria、React Spectrum
出典:React Aria Blog — Creating an accessible autocomplete experience(新しいタブで開きます)
スピンボタン
NumberField は role="spinbutton" を使わない
- APG では
- カスタムの数値入力には role="spinbutton" と aria-valuenow / aria-valuemin / aria-valuemax を付け、↑↓ キーで増減できるようにする。
- 現場では
- React Aria や Base UI の NumberField は spinbutton ロールを付けず、type="text"(inputMode="numeric")の入力欄に aria-roledescription(例: "Number field")を添える。増減キーは実装しつつ、支援技術には「説明つきのテキスト入力」として公開する。
- なぜ
- React Aria のドキュメントは「spinbutton の ARIA パターンに従う」と述べつつ、同じ箇所で「VoiceOver の spinbutton ロールに起因するバグを回避している」と明記している。加えて通貨・パーセント・単位付きなど多様な数値フォーマットを国際化対応で扱うため、input type="number" ではなく type="text" + inputMode + カスタムの role description を採用した経緯がブログで説明されている。つまり「APG どおりのロールを額面どおり使うと実機のスクリーンリーダーで壊れる」という実測に基づく回避策であり、Base UI も同じ構成を採用している。
採用:React Aria、Base UI
出典:React Aria — useNumberField(Accessibility features)(新しいタブで開きます)、React Aria Blog — How we internationalized our number field(新しいタブで開きます)、Base UI — Number Field(新しいタブで開きます)
タブ
MUI は APG が基本とする「自動アクティベーション」をデフォルトにしていない
- APG では
- 「関連するタブパネルが目立つ遅延なく表示される限り、フォーカス受信時に自動でタブを有効化することを推奨する」——つまり自動が基本で、手動は表示に遅延がある場合の代替という位置づけ。
- 現場では
- Radix・React Aria・Headless UI は APG どおり自動をデフォルトにする一方、MUI(Material UI)はデフォルトを手動アクティベーションにしており、自動へ切り替えるには selectionFollowsFocus prop の明示が必要。
- なぜ
- MUI は公式ドキュメントで「多くの場合、手動アクティベーションのほうが望ましい挙動」と述べ、パネルの描画コストや意図しない切り替わりを避けるために保守的なデフォルトを選んでいる。各ライブラリのドキュメントでも「タブ切替に重い処理(データ取得・大きな再描画)が伴うなら手動」という判断基準は共通しており、自動/手動の分岐点はパネル描画コストにある、という実装間の共通認識が確立している。APG の推奨は「遅延なく表示される限り」という条件つきであることを読み落とさないのが実務のポイント。
採用:MUI (Material UI)
出典:W3C APG — Tabs Pattern(新しいタブで開きます)、MUI — React Tabs(新しいタブで開きます)、Radix Primitives — Tabs(新しいタブで開きます)
グリッド
grid パターンは「Excel 的な編集」以外では避け、素の <table> を選ぶ
- APG では
- セル単位の選択や矢印キーでの2次元ナビゲーションが必要な対話的な表には grid / treegrid パターンを用い、roving tabindex で表全体を1タブストップにする。
- 現場では
- Adrian Roselli は「grid は Excel 類の複雑な表計算を再現する場合以外は無視すべき」と明言し、リンクやボタンを含む程度の表なら grid ロールなしの素の <table> で十分としている。Sarah Higley も「操作が主目的か、情報の閲覧が主目的か」で判断すべきとし、視覚的にグリッド状でも行列に意味がなければ grid 化すべきでないと具体例つきで論じている。
- なぜ
- grid ロールを宣言すると矢印キーの2次元移動や Tab ストップ制御など追加のキーボード処理一式が必須になるが、不完全なまま公開される実装が多い。ユーザーテストでは「予期しない矢印キー挙動に戸惑う」「レスポンシブ表示でレイアウトと矢印ナビゲーションの列情報が食い違う」といった問題が観察された。「クリック可能な行がある」程度の理由で table を grid に変えるのは過剰実装で、素の table のほうが列見出しの読み上げなども自然に機能する——という実測ベースの結論が両者に共通している。
採用:Adrian Roselli、Sarah Higley
出典:Adrian Roselli — ARIA Grid As an Anti-Pattern(新しいタブで開きます)、Sarah Higley — Grids Part 1: To grid or not to grid(新しいタブで開きます)
React Aria の Calendar は grid ナビゲーションを意図的に「削って」いる
- APG では
- grid パターンでは、行・列ヘッダーを含むグリッド全体を矢印キーでナビゲートできることが基本とされる。
- 現場では
- React Aria の Calendar は grid パターンに従いつつ、曜日ヘッダー行をスクリーンリーダーのナビゲーション対象からスキップする。さらに月をまたぐ移動では、最初のセルまで戻らなくても次月へ進めるよう、視覚的に隠した「次へ」ボタンを独自に追加している。
- なぜ
- モバイルのスクリーンリーダーでは要素を順に辿る操作が基本のため、APG どおりグリッド全体を舐めさせると冗長で非効率になることが実測で分かった。ヘッダーのスキップも隠しボタンの追加も「パターンの完全性」より「実際の移動効率」を優先した工夫。同じ理由で日付入力も、APG が想定する自由入力のテキストフィールドではなく、年月日それぞれが個別にフォーカス可能な「セグメント」方式を採っている。
採用:React Aria、React Spectrum
出典:React Aria Blog — Date and Time Pickers for All(新しいタブで開きます)
ツールチップ
role="tooltip" と aria-describedby を付けず「視覚専用のヒント」と割り切る
- APG では
- ツールチップ本体に role="tooltip" を付け、トリガーから aria-describedby で参照する。ホバーとフォーカスの両方で表示し、Esc で閉じられるようにする。タッチ入力については言及がない。
- 現場では
- Base UI の Tooltip は role="tooltip" も aria-describedby も付けず、ドキュメントで「ツールチップは視覚的な補助であり、必須情報はトリガーの aria-label 等で直接提供すべき」と明言。さらにタッチデバイスでは表示自体を無効化し、必須情報には Popover(openOnHover)を使うよう誘導している。Material UI もタッチでの誤発火防止に enterTouchDelay(デフォルト 700ms の長押し)を設ける。
- なぜ
- ツールチップは hover / focus 依存で設計されているが、タッチデバイスにはどちらも存在しない(タップは即アクティベートになる)。つまり「ツールチップにしか無い情報」は一部のユーザーに構造的に届かない。Sarah Higley が 2019 年にこの非互換を指摘して以来仕様側は未解決のままで、APG に忠実な Radix には「タッチで開かない」issue が繰り返し報告されている。Base UI チームはこの構造問題を踏まえ、「トリガーの目的が開閉自体か否か」という判断基準を明文化し、ツールチップを冗長な視覚ヒントに限定する設計判断をした。
採用:Base UI、Material UI
出典:Base UI — Tooltip(Accessibility guidelines)(新しいタブで開きます)、Sarah Higley — Tooltips in the time of WCAG 2.1(新しいタブで開きます)、MUI — Tooltip(新しいタブで開きます)
Ariakit は aria-describedby ではなく aria-labelledby をデフォルトにした
- APG では
- トリガー要素はツールチップを aria-describedby で参照する。ツールチップは補足説明であり、アクセシブルネームの提供元ではないという前提。
- 現場では
- Ariakit は v2 でツールチップのデフォルト関連付けを aria-labelledby に変更した。説明として使いたい場合のみ、利用側が明示的に aria-describedby へ切り替える設計になっている。
- なぜ
- Web の実態として「アイコンのみでラベルの無いボタン + ツールチップで名前を示す」実装が非常に多い。この使い方で aria-describedby にするとボタンにアクセシブルネームが無く、実機検証では VoiceOver が「Undo, button, Undo」と重複読みし、NVDA と読み上げが一貫しないことをメンテナが確認した。aria-labelledby なら常に安定して読み上げられるため、APG の規定より「実際の使われ方」を優先した。なお本来は role="tooltip" 自体を外すべきという議論も残っており、チーム自身が仕様との差分を認識した上での選択。
採用:Ariakit
出典:Ariakit — Tooltip(新しいタブで開きます)、ariakit/ariakit#2228 — Tooltip accessibility(新しいタブで開きます)
APG の tooltip パターン自体が「合意未了」のまま — 現場は toggletip へ
- APG では
- APG のツールチップパターンには「work in progress; タスクフォースの合意に至っていない」と明記されており、2016 年から未解決のまま。クリックで開閉する toggletip は APG のパターン一覧に存在しない。
- 現場では
- Sarah Higley は role="tooltip" を「ロールの中の愛されない子(unloved child of roles)」と呼び、必須でない補足情報にのみツールチップを使うよう推奨。Heydon Pickering はタッチ・マウス・キーボードすべてで動く toggletip(クリックで開閉する説明ボックス)を代替として提唱している。
- なぜ
- hover / focus 依存という構造的欠陥に加え、WCAG 2.1 の 1.4.13(ホバー又はフォーカスで表示されるコンテンツ)が dismissable / hoverable / persistent の3要件を課したことで、APG 型ツールチップの実装コストは大きく跳ね上がった。W3C ARIA WG 自身も issue やTPAC 2023 の議論で「現行の tooltip の定義ではリッチなコンテンツを含むユースケースを表現しきれない」とロールの見直しが必要と認めている。ただし toggletip にも「本質的な情報を小さなボタンの裏に隠すべきではない」という批判があり、現場でも完全なコンセンサスではなく有力な代替案の一つという位置づけ。
採用:Sarah Higley、Heydon Pickering (Inclusive Components)
出典:Sarah Higley — Tooltips in the time of WCAG 2.1(新しいタブで開きます)、Inclusive Components — Tooltips & Toggletips(新しいタブで開きます)、w3c/aria-practices#128 — Tooltip pattern consensus(新しいタブで開きます)