注文1042
10月14日にお届け済み
「詳細」ボタンを押すと、クリックできるdivを並べたメニューが開きます。しかしボタンはメニューについて何も伝えず、フォーカスもボタンに残ったままです。各操作には役割がなく、Tabでも矢印キーでも移動できないため、キーボードでは操作に届かず、スクリーンリーダーには何かが開いたことが伝わりません。
<div class="item">領収書をダウンロード</div> キーボード利用者は「詳細」のメニューを開けても中の何にも届かず、スクリーンリーダー利用者には、ボタンがメニューを開くことも、メニューが開いたことも伝わらない
あるショップの注文履歴では、注文ごとにカードが並び、それぞれに「詳細」ボタンがあります。押すと、「もう一度購入」「返品する」「領収書をダウンロード」「問題を報告」という、その注文でできる4つの操作が下に開きます。「詳細」をクリックするとボタンの下にメニューが開き、「領収書をダウンロード」をクリックすると領収書がダウンロードされます。ボタンは本物のbuttonなので、Tabキーで届き、Enterキーでメニューが開きます。ところがフォーカスは「詳細」に残ったままで、各操作はクリック処理を付けたdivです。次にTabキーを押すと、開いたメニューを飛ばして、カードの下の「すべての注文を見る」へ移ります。下矢印キーはページをスクロールするだけです。メニューの中には、マウスなしでは何も届きません。スクリーンリーダーは「詳細、ボタン」と読み、Enterキーを押しても新しく何も読まれません。ボタンがメニューを開くことも、メニューが開いたことも伝わりません。スクリーンリーダー自身のキーで読み進めると、4つの操作はただの文字として出てきます。押せるものなのか、どのボタンのものなのかを示す役割はありません。
そこでチームは、手っ取り早くメニューに仕立てます。ボタンに aria-haspopup="menu"、パネルに role="menu"、各操作に role="menuitem" を付け、それ以外は何もしません。スクリーンリーダーはボタンがメニューを開くと伝えるようになりますが、押した後は何も変わりません。フォーカスはボタンに残り、メニューが開いたことは伝わらず、Tabキーは今もすべての項目を飛ばし、メニューが約束する矢印キーは何もしません。
達成基準2.1.1「キーボード」は、マウスでできることはすべてキーボードでもできることを求めています。マウスはdivをクリックして操作を実行しますが、divはフォーカスを受けないため、キーボードではメニューを開けても中の何にも届きません。「領収書をダウンロード」を含め、メニューのどの操作にもマウスが必要です。メニューのロールを足しても、この点は何も変わりません。ロールは要素が何であるかを伝えるもので、どう動くかは変えません。項目は今もフォーカスを受けず、矢印キーに応えるスクリプトもありません。
達成基準4.1.2「名前(name)・役割(role)及び値(value)」は、コントロールの役割と状態が支援技術に伝わり、その変化も伝わることを求めています。ボタンの名前と役割は伝わっていますが、メニューを開くことも、メニューが開いているかどうかも伝わっていないため、押しても、スクリーンリーダーが読み上げられる変化が何も起きません。各操作には役割がまったくありません。divにクリック処理を付けても、アクセシビリティAPIに役割は伝わらず、これは失敗例F59にあたります。ロールを足すと、ボタンはメニューを開くことを伝えますが、開いているかどうかは今も伝えず、フォーカスもボタンに残るため、メニューが開いても何も聞こえないままです。ロールが約束する動きのないロールは、アクセシビリティAPIの使い方が不完全なカスタムのコントロールとして、失敗例F15にあたります。
操作メニューは、注文履歴、住所録、ファイルの一覧、チームの設定、コメントなどにあり、行ごとに1つずつ置かれることもよくあります。スマートフォンでは、画面に並べきれない操作を同じボタンにまとめることも多くあります。その奥にあるものは、クリックできない人には届かず、画面を見られない人には開いたことさえ分かりません。
10グループ中2グループに影響
最初のメニューには名前のある本物のbuttonと、役割のないdivしかないため、axeが報告することはありません。メニューのロールもaxeに合格します。どの menuitem も menu の中にあり、どれにも名前があるからです。axeが確かめるのはロールの組み合わせが正しいかで、フォーカスが項目に届くかや、キーが何をするかは確かめません。
この診断ツールはキーを押します。aria-haspopup でメニューを開くと伝えるボタンと、「More」「Actions」「Options」「詳細」「その他」「その他の操作」「操作」という名前のボタンや、kebab、overflow、dropdownなどのクラスを持つボタンを、サイトのナビゲーションとヘッダーの外から探します。それぞれを読み込み直したページでEnterキーを押して開き、現れたものを調べます。フォーカスがメニューに移らず、下矢印キーでもTabキーでも届かず、どの操作もTabの移動先でなければ、報告します。フォーカスを受けても、矢印キーでもHomeやEndでも、フォーカスが届いた項目から動けない role="menu" も報告します。メニューが開いてもフォーカスを自分に残し、aria-expanded のないボタンも報告し、aria-expanded がfalseのまま変わらないものも報告します。項目がTabの移動先で、矢印キーが何もしないメニューは、人が確認する項目として渡します。ほかの名前で、それと分かる属性やクラスのないメニューボタンや、ヘッダーの中のものは見つからず、並んでいる操作が利用者に必要なものかは、どの確認でも分かりません。
2つ目のメニューは、AIに直させるとこうなることがある形です。今あるdivにロールを足しただけで終わり、それでもaxeには合格します。すべての項目に tabindex="0" を付ける修正もあります。項目はそれぞれTabの移動先になりますが、矢印キーは何もしないメニューのままです。
キーボードだけで「詳細」を開き、「領収書をダウンロード」まで移動してみましょう
問題1: Tabキーで「詳細」へ移り、Enterキーを押します。メニューは開きますが、フォーカスはボタンに残り、下の欄には新しく何も読まれず、次のTabキーはメニューを飛ばします。
問題2: 同じdivに、メニューのロールを足しただけです。ボタンがメニューを開くことは読まれるようになりましたが、Enterキーを押してもフォーカスは残り、Tabキーでも矢印キーでも項目に届きません。
修正を適用します。 メニューボタンでは、Enterキーで最初の操作へフォーカスが移り、矢印キー、Home、End、Escapeが使えます。開閉ボタンでは、「詳細」の次のTabキーでリストの中へ入ります。
問題あり 修正できますか?
注文1042
10月14日にお届け済み
スクリーンリーダーの読み上げ:
(Tabで「詳細」へ移動)
ボタンで何かを開くことと開閉の状態を伝え、操作はすべて、フォーカスと矢印キーを受けるメニューか、ボタンのすぐ後ろに並べた普通のボタンにして、キーボードで届くようにする
ボタンと、クリックできるdivのドロップダウン
<article class="order"> <h2>注文1042</h2> <p>10月14日にお届け済み</p> <div class="actions"> <button type="button" class="actions-button">詳細</button> <div class="actions-menu" hidden> <div class="item" data-action="buy-again">もう一度購入</div> <div class="item" data-action="return">返品する</div> <div class="item" data-action="invoice">領収書をダウンロード</div> <div class="item" data-action="report">問題を報告</div> </div> </div></article>メニューボタン
<article class="order"> <h2>注文1042</h2> <p>10月14日にお届け済み</p> <div class="actions"> <button type="button" class="actions-button" id="order-1042-actions" aria-haspopup="menu" aria-expanded="false" aria-controls="order-1042-menu">詳細</button> <div class="actions-menu" id="order-1042-menu" role="menu" aria-labelledby="order-1042-actions" hidden> <button type="button" role="menuitem" tabindex="-1" data-action="buy-again">もう一度購入</button> <button type="button" role="menuitem" tabindex="-1" data-action="return">返品する</button> <button type="button" role="menuitem" tabindex="-1" data-action="invoice">領収書をダウンロード</button> <button type="button" role="menuitem" tabindex="-1" data-action="report">問題を報告</button> </div> </div></article>ボタンはメニューを開きますが、フォーカスを中へ移す処理はなく、各操作は click にしか応えずフォーカスも受けないため、キーボードはボタンで止まります。ボタンが何かを開くことも、開いているかどうかも、その下の文字が操作であることも、マークアップのどこにも書かれていません。
ARIA Authoring Practicesのメニューボタンのパターンです。aria-haspopup="menu" で、スクリーンリーダーはボタンがメニューを開くことを伝え、aria-expanded で開いているかどうかを伝えます。Enterキー、Spaceキー、クリックでメニューが開き、フォーカスが「もう一度購入」へ移るため、スクリーンリーダーはボタンの名前が付いたメニューと、その項目を読み上げます。多くの場合、「4項目中1」のような位置も読まれます。項目はbuttonなので、キーの処理を書かなくてもEnterキーとSpaceキーで実行でき、role="menuitem" でメニューの項目としての役割を持ちます。tabindex="-1" で項目をTabの移動先から外し、スクリプトで項目の間のフォーカスを動かします。下矢印キーと上矢印キーで移動し、端では反対側へ回り、HomeとEndで最初と最後へ移ります。Escapeキーを押すか操作を実行すると、メニューが閉じてフォーカスが「詳細」に戻ります。Tabキーを押すとメニューを出てページの次の部品へ進み、メニューは閉じます。
どちらも基準を満たします。小さな変更で済むのは開閉ボタンで、この例のように、サイトのページにある少数の操作に向いています。操作は、どのブラウザーもスクリーンリーダーも音声操作のソフトもすでに知っているボタンで、属性1つと開閉の処理だけで作れ、ページのほかの部分と同じようにTabキーで移動できます。メニューボタンは、メールソフト、ファイル管理、エディターのように、アプリケーションとして動くページで作ります。操作がOSのメニューと同じように動くことが期待され、操作をすべてTabの移動先にすると、どの行も通り過ぎるのに時間がかかる場面です。その場合は、ロール、開いている状態、中へ移るフォーカス、すべてのキーと、全部が必要です。手本は、ARIA Authoring PracticesのActionsメニューの例です。カードに余裕があれば、操作を隠さないことも考えてください。右クリックや長押し、スワイプでしか出ない操作メニューで示しているように、操作ごとにボタンを置く方法です。
注文の一覧では、どのカードのボタンも「詳細」です。ボタンの文字はそのままにして、どの注文のものかを加えます。カードの見出しを aria-describedby で指すか、文字の後ろに隠したテキストを足し(詳細<span class="visually-hidden">(注文1042)</span>)、名前が画面に見える文字で始まるようにします。表示と名前が食い違うボタンとリンクで説明しているとおりです。メニューは、aria-labelledby でボタンから名前を得ます。
メニューボタンのパターンでは、ほかにもできることがあります。ボタンで下矢印キーを押すとメニューを開いて最初の項目へ、上矢印キーなら最後の項目へ移る動きも認められています。文字を押すと、その文字で始まる次の項目へ移る方法もあり、長いメニューで役立ちます。今は使えない操作は、aria-disabled="true" を付けてメニューに残します。そうすれば、その操作があることが伝わり、矢印キーでも届きます。オンとオフを切り替える操作は、aria-checked を付けた menuitemcheckbox にします。操作メニューにサブメニューは入れないでください。サブメニューごとに、開く右矢印キーと出る左矢印キー、画面上の配置と、正しく作るべきものが増えます。
role="menu" に入れるのはメニュー項目だけで、リンク、入力欄、見出しを並べることはできません。それらが必要なら、開閉ボタンかダイアログにします。「領収書を見る」のようにほかのページへのリンクは、開閉ボタンのリストならリンクのまま置けますが、メニューの中では role="menuitem" も必要です。
どちらのメニューも、popover 属性で作れます。その場合、ブラウザーがEscapeキーとメニュー外のクリックで閉じ、Escapeキーで閉じたときはフォーカスをボタンに戻し、ページの上に描くため、カードの overflow: hidden で切れることもありません。右クリックや長押し、スワイプでしか出ない操作メニューのメニューは、この方法で作っています。ポップオーバーは、CSSのアンカー配置か数行のスクリプトで位置を決めるまでは、画面の中央に表示されます。使わない場合は、カードがメニューを切らないかを確かめます。一部の部品ライブラリーのように、メニューをページの末尾に描く場合、スクリプトで中へ移すフォーカスはそれでも届きますが、開閉ボタンのリストにはほかのすべての後でしか届きません。フォーカス順の最後に回る、フローティングボタンとパネルで説明しています。どちらも閉じたときにフォーカスがどこへ行くかは、Tabで閉じてフォーカスを見失う検索パネルで扱っています。
ライブラリーのメニューの部品はさまざまです。フォーカスを移してすべてのキーを扱うものもあれば、ロールを付けるだけのものもあります。使う前に、キーボードで確かめてください。
5項目、マウス不要
確認方法: キーボード、スクリーンリーダー、強制配色、拡大表示
OS、ブラウザーと支援技術のバージョン、ビルド、実施日、各手順の実際の結果を記録してください。メニュー、その部品、ライブラリーを変更した際は、再度確認します。
スクリーンリーダーでの aria-expanded、aria-haspopup="menu"の読まれ方
このサンプルは、どのアプリケーションでも同じ結果になることを保証するものではありません。スクリプトは最小限の例で、ページにメニューが1つだけの場合を想定し、先頭文字での移動、使えない項目、サブメニューはありません。サイト上部に並ぶリンクのドロップダウンはナビゲーションで、別のパターンです。マウスでしか開かないか、開閉が伝わらないドロップダウンメニューで扱っています。サイズのように値を選ぶドロップダウンはセレクトで、キーボードで開けず、読み上げられないカスタムのドロップダウンで扱っています。右クリックや長押しでしか開かないメニューは右クリックや長押し、スワイプでしか出ない操作メニューで扱っています。3つの点のようにアイコンだけのボタンには名前も必要で、名前がないアイコンだけのボタンで説明しています。対象とする支援技術で、一連の操作全体をテストしてください。
よくある問題を再現した学習用のサンプルです。実在のクライアントの診断結果ではありません。コードは出発点となる実装例のため、実際の製品でも確認してください。ここでは支援技術でのテスト結果は報告していません。
ガイドの内容を、コーディングエージェントやCIで使える開発ルールにまとめます。更新後は、キーボードとスクリーンリーダーで再テストし、問題が再発していないか確認します。