トピックを検索
マウスでしか開かないドロップダウンメニュー
上段の項目の下にあるサブメニューが:hoverのルールだけで表示されるため、Tabキーでは中のページをすべて飛ばしてしまいます。
.has-sub:hover > ul { display: block; } 問題
キーボードやスクリーンリーダーの利用者が、上段の項目の下にあるページにたどり着けません。
家具店のヘッダーに「ホーム」「商品」「会社概要」の三つの項目があります。「商品」にマウスを重ねると、その下に「照明」「椅子」「ラグ」の一覧が開きます。マウスを使わずTabキーを押すと、フォーカスは「ホーム」「商品」「会社概要」と移動するだけで、一覧は開きません。画面にもスクリーンリーダーにも、一覧があることを示すものはありません。「商品」でEnterキーを押すと一覧ページへ移動するため、店にはたどり着けますが、各カテゴリーのページはさらに何回か操作した先にあるか、ほかのどこからもリンクされていません。
サブメニューは display: none で隠され、.has-sub:hover > ul という一つのルールでだけ表示されます。マウスを重ねる操作はポインターにしかできないため、隠れている間は中のリンクにフォーカスが移らず、キーボードでは表示させる方法がありません。WCAG 2.1.1 キーボードは、ページでできることをすべてキーボードでもできるよう求めており、これらのリンクへの移動もその一つです。また、上段の項目にはサブメニューがあることも、開いているかどうかも示されていません。4.1.2 名前・役割・値は、操作部品として働くものにこの情報を求めています。
タッチ画面にもマウスを重ねる操作はありません。最初のタップで一覧を開き、2回目でリンク先へ移動するブラウザーもあれば、すぐにリンク先へ移動するブラウザーもあるため、スマートフォンで一覧が見えるかどうかはブラウザーによって変わります。
10グループ中2グループに影響
自動チェックが読むのはマークアップで、nav 内のリストにある正しいリンクとして判定します。マウスを重ねないため、サブメニューが開く様子は確認されず、マウスで届く範囲とTabキーで届く範囲も比べられません。閉じた内容を隠すのは正しいことなので、隠れたリンクは報告されません。AIのコーディングツールにメニューを使いやすくするよう頼むと、role="menubar" や role="menuitem" を追加することがよくあります。これは修正になりません。これらのロールはアプリケーションのメニューと同じ矢印キーの操作を約束するもので、その操作はコードで用意する必要があります。サイトのナビゲーションに使うと、スクリーンリーダーの操作モードが切り替わり、メニューではないものがメニューとして読み上げられます。リンクに aria-haspopup="true" を追加する例もありますが、ポップアップがあると読み上げられるだけで、開けないことは変わりません。マウスを置いてヘッダーをTabキーで移動してみることが、この問題を見つける唯一の方法です。
試してみる
TabとEnterだけで「照明」まで移動してください。
マウスを「商品」に重ねて一覧を確かめてから、キーボードで試してください。一覧が開いたらEscapeも試し、「会社概要」の先へTabで進んでください。一覧が次のリンクを覆いますか?
問題あり 修正できますか?
今週のおすすめ:春のセール
スクリーンリーダーの読み上げ:
(Tabでメニューへ移動)再現デモです。「問題あり」は意図的にアクセシブルでない状態にしています。
修正方法
どのサブメニューもキーボードで開けるようにし、開いているかどうかを伝えます。
- 1サブメニューは、button、summary、キーボードのフォーカスのいずれかで、マウスなしで開く。
- 2サブメニューを開く操作部品は、開閉状態を伝える。
- 3ページに重なって開くサブメニューは、Escapeで閉じ、フォーカス中の要素を隠さない。
<nav aria-label="メイン"> <ul class="menu"> <li><a href="/">ホーム</a></li> <li class="has-sub"> <a href="/products">商品</a> <ul> <li><a href="/products/lamps">照明</a></li> <li><a href="/products/chairs">椅子</a></li> <li><a href="/products/rugs">ラグ</a></li> </ul> </li> <li><a href="/about">会社概要</a></li> </ul></nav>サブメニューを開くbutton
<nav aria-label="メイン"> <ul class="menu"> <li><a href="/">ホーム</a></li> <li class="has-sub"> <button type="button" aria-expanded="false" aria-controls="sub-products">商品</button> <ul id="sub-products" tabindex="-1" hidden> <li><a href="/products">すべての商品</a></li> <li><a href="/products/lamps">照明</a></li> <li><a href="/products/chairs">椅子</a></li> <li><a href="/products/rugs">ラグ</a></li> </ul> </li> <li><a href="/about">会社概要</a></li> </ul></nav>この方法を選ぶ理由:まずHTMLの要素を使う
三つのカテゴリーへのリンクは、ポインターが「商品」に重なるまで display: none のため、Tabキーは「商品」から「会社概要」へ移動します。「商品」にサブメニューがあることも、マークアップには何も書かれていません。
HTML標準のbuttonはEnterとSpaceで開閉でき、aria-expanded によって、スクリーンリーダーは「商品、ボタン、折りたたみ」、開くと「展開」と読み上げます。マークアップ上でリストはボタンの直後にあるため、Tabキーでボタンから開いたリストへそのまま移動できます。閉じている間は hidden によってリンクがTab順から外れます。Escapeでリストを閉じ、フォーカスを「商品」に戻します。Tab、Shift + Tab、ほかの場所のクリックでフォーカスが項目から離れると、フォーカスを動かさずにリストを閉じるため、開いたリストが次の移動先を覆うことはありません。リストの tabindex="-1" により、リスト内をクリックしてもフォーカスが項目の内側に残り、離れたとは見なされません。ボタンは一覧ページへ移動しなくなるため、そのページは「すべての商品」としてリストに入れます。
buttonの方法を使います。周りのページに左右される条件が残らず、それだけで適合します。開閉状態を伝え、Escapeとフォーカスが離れたときに閉じ、サブメニューごとのTabの移動先は一つだけです。details と summary はスクリプトが不要で、開いたリストが下の内容を押し下げる場合には適した方法ですが、ページに重なる場合は利用者が離れた後も開いたままになります。:focus-within で開く方法は既存のメニューへの最小限の変更で、buttonを作るまでの間、最も深刻な問題を止められます。ただし、Escapeで閉じられず、スクリーンリーダーの利用者にリストがあることも伝わりません。
上段の項目自体もページである場合は、どうするかを決める必要があります。例のように一覧ページを「すべての商品」としてリストに移すと、項目ごとの操作部品は一つで済みます。項目をリンクのまま残す必要がある場合は、リンクと小さな別のbuttonを並べ、buttonには「商品のページ」のような名前を付けて、2つ目の「商品」と聞こえないようにします。
実際のメニューでは、ほかの部分についても判断が必要です。
- マウスを重ねて開く操作:マウス利用者のために残してもかまいません。その場合はマウスを重ねると現れる内容になり、1.4.13により、ポインターを移しても開いたままで、Escapeで閉じる必要があります。詳しくはホバーで表示される内容を参照してください。
- 矢印キー:サイトのメニューでは任意です。この開閉パターンはTabキーだけで使えます。上段のボタン間を矢印キーで移動できるようにする場合も、Tabキーでの移動は残します。
aria-haspopup:付けません。buttonに付けるとメニューとして読み上げられますが、このリストはリンクの集まりで、メニューではありません。- スマートフォン:狭い画面のメニューでも同じbuttonが使え、リストは縦に並んで下の内容を押し下げます。メニュー全体を開く「メニュー」ボタンも同じパターンに従います。
- 現在のページ:カテゴリーのページを表示しているときは、そのリンクに
aria-current="page"を付けます。現在のページで説明しています。
AI コーディングアシスタントで修正しますか? このガイドを Markdown で取得
修正の確認
マウスを使わない5つの確認
確認方法: キーボード、スクリーンリーダー、ズーム、タッチ
OS、ブラウザーと支援技術のバージョン、ビルド、実施日、各手順の実際の結果を記録してください。共通のヘッダーやメニューのコンポーネントを変更した際は、再度確認します。
制限
このサンプルはサイトのナビゲーションメニューのための出発点で、コマンドを並べるアプリケーションのメニューは対象外です。そうしたメニューはARIAのメニューパターンと矢印キーの操作に従います。スクリプトは、アニメーション、マウスを重ねて開く操作、入れ子の階層を含まない最小限の例です。狭い画面のメニューも含めて、実際に使用する支援技術で操作全体を確認してください。
よくある問題を再現した学習用のサンプルです。実在のクライアントの診断結果ではありません。コードは出発点となる実装例のため、実際の製品でも確認してください。ここでは支援技術でのテスト結果は報告していません。
参考資料
- WCAG Understanding: keyboard(新しいタブで開きます)
- WCAG Understanding: name-role-value(新しいタブで開きます)
- ARIA APG: disclosure-navigation(新しいタブで開きます)
- WAI Tutorial: menus/flyout(新しいタブで開きます)
- WCAG Understanding: content-on-hover-or-focus(新しいタブで開きます)
- WCAG Understanding: focus-not-obscured-minimum(新しいタブで開きます)
- developer.mozilla.org: details(新しいタブで開きます)
コードを更新しても、使いやすさを保つために
ガイドの内容を、コーディングエージェントやCIで使える開発ルールにまとめます。更新後は、キーボードとスクリーンリーダーで再テストし、問題が再発していないか確認します。