ナビゲーション
メニュー / メニューバー
解説ありMenu and Menubar
アプリ的なメニュー。矢印キー操作と role="menu" 系のロールで構成します。
メニューバー(Menubar)とは?
メニューバーは、アプリ画面の上部に「ファイル/編集/表示…」と並ぶ、 あの水平のメニューです。各項目を開くとサブメニュー(コマンドの一覧)が出てきます。 デスクトップアプリのような機能豊富なコマンド体系を提供する場面で使います。
重要:サイトのナビゲーションに menu role を使わない
一番やりがちな誤りが、サイト内リンクのナビゲーションをrole="menubar" / role="menu" / role="menuitem" にすることです。 これらの role はアプリのコマンド(実行すると何かが起きる操作)のためのものです。
- ページへ移動する「リンク集」は
<nav>+<ul>+<a>で作る。 role="menu"にすると、スクリーンリーダーは「メニュー=矢印で操作するコマンド群」と案内するのに、 中身はただのリンクで挙動が食い違い、利用者が混乱します。- サブメニュー付きのサイトナビは、
<button aria-expanded>で開閉する 「ディスクロージャー・ナビゲーション」にするのが定石です。
なぜアクセシビリティが大事なの?
- キーボードだけで操作する人。メニューバーは Tab 1回で入り、←→ でバーを移動、↓/Enter でサブメニューを開き、↑↓ で項目を選び、Esc で閉じる、という一連の操作が必要です。
- スクリーンリーダーを使う人。
role="menubar"/role="menu"/role="menuitem"とaria-haspopup/aria-expandedによって、構造と開閉状態が正しく伝わります。
ライブデモ(推奨実装)
下はアプリ風のメニューバー(テキストエディタを想定)です。コマンドを実行する例なのでmenu role が適切です。キーボードで操作してみてください。
- 新規作成
- 開く
- 保存
- 元に戻す
- やり直す
- すべて選択
- 拡大
- 縮小
試してみよう:Tab でバーに入る → ← → でメニュー移動 → ↓ / Enter でサブメニューを開く → ↑ ↓ で項目移動、Home / End で端へ → サブメニュー内で ← → を押すと隣のメニューへ → Enter で実行 → Esc で閉じて親に戻る。
キーボード操作
| キー | 動作 | 必須/任意 |
|---|---|---|
| Tab | メニューバーに入る/出る(バー全体で1タブストップ) | 必須 |
| ← / →(バー上) | 前 / 次のメニューへ移動 | 必須 |
| ↓ / Enter / Space(バー上) | サブメニューを開き、最初の項目へ | 必須 |
| ↑ / ↓(サブメニュー内) | 前 / 次の項目へ移動 | 必須 |
| ← / →(サブメニュー内) | 現在のメニューを閉じ、隣のメニューを開く | 任意(推奨) |
| Home / End | 最初 / 最後の項目へ移動 | 任意 |
| Enter / Space(項目上) | コマンドを実行して閉じる | 必須 |
| Esc | サブメニューを閉じ、親のメニュー項目へ戻る | 必須 |
必要な WAI-ARIA / ロール
| 付ける場所 | 属性 / ロール | 意味 |
|---|---|---|
| バーの箱 | role="menubar" + aria-label | 水平のメニューバーであることと名前を伝える。 |
| バーの各項目 | role="menuitem" + aria-haspopup="menu" | サブメニューを開くメニュー項目であることを伝える。 |
| バーの各項目 | aria-expanded + tabindex="0 | -1" | 開閉状態と roving tabindex(1つだけ 0)。 |
| サブメニュー | role="menu" + aria-label | サブメニューであることと名前を伝える。 |
| サブメニューの項目 | role="menuitem" + tabindex="-1" | コマンド項目。フォーカスは JS で管理(roving)。 |
| 閉じたサブメニュー | hidden | 閉じている間は読み上げ/操作の対象外にする。 |
実装:推奨パターン(Good)
良い例 / 推奨
水平の role="menubar" に role="menuitem" を並べ、roving tabindex で1タブストップに。 サブメニューは role="menu" で、開閉と Esc 復帰、←→ での メニュー移動を JS で実装します。
マークアップ:
<div role="menubar" aria-label="テキストエディタ">
<button type="button" role="menuitem"
aria-haspopup="menu" aria-expanded="false" tabindex="0">
ファイル
</button>
<ul role="menu" aria-label="ファイル" hidden>
<li role="menuitem" tabindex="-1">新規作成</li>
<li role="menuitem" tabindex="-1">開く</li>
<li role="menuitem" tabindex="-1">保存</li>
</ul>
<button type="button" role="menuitem"
aria-haspopup="menu" aria-expanded="false" tabindex="-1">
編集
</button>
<ul role="menu" aria-label="編集" hidden>
<li role="menuitem" tabindex="-1">元に戻す</li>
<li role="menuitem" tabindex="-1">やり直す</li>
</ul>
</div>キー操作の骨子:
// 上位(バー)の menuitem は roving tabindex で1タブストップ。
// ← → でバー内を移動、↓/Enter でサブメニューを開いて先頭へ。
// サブメニュー内は ↑↓ で移動、Esc で閉じて親へ戻す、← → で隣のメニューへ。
top.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight') moveTop(+1);
else if (e.key === 'ArrowLeft') moveTop(-1);
else if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ')
openSub(top, 0); // 開いて先頭の項目へ
else if (e.key === 'ArrowUp') openSub(top, 'last');
});
subItem.addEventListener('keydown', (e) => {
if (e.key === 'ArrowDown') focusNextItem();
else if (e.key === 'ArrowUp') focusPrevItem();
else if (e.key === 'Escape') { closeSub(); top.focus(); } // 親へ戻す
else if (e.key === 'ArrowRight' || e.key === 'ArrowLeft')
moveToAdjacentMenu(e.key); // 隣のメニューへ
else if (e.key === 'Enter' || e.key === ' ') run(subItem);
});アンチパターン(Bad)
下はサイトのナビゲーションに role="menubar" を付け、サブメニューを:hover で開く例です。役割の誤用と hover 依存が重なっています。
試してみよう:「製品」にマウスを乗せると開きますが、キーボードではサブメニューを開けません。スクリーンリーダーは「メニュー(矢印で操作)」と案内するのに中身はただのリンクで、挙動がかみ合いません。
<!-- ❌ アンチパターン:サイトナビに menu role を付け、hover で開く -->
<nav>
<ul role="menubar">
<li role="menuitem">製品
<!-- hover でしか開かない & リンクなのに menu 化で読み上げが壊れる -->
<ul role="menu" class="sub">
<li role="menuitem"><a href="/a">機能A</a></li>
<li role="menuitem"><a href="/b">機能B</a></li>
</ul>
</li>
<li role="menuitem"><a href="/price">料金</a></li>
</ul>
</nav>
<style>
.sub { display: none; }
li:hover .sub { display: block; } /* hover 依存 */
</style>悪い例 / 避ける
この実装の問題点:
- role の誤用 — ページ移動のリンク集に
menubar/menuを付けると、 支援技術の案内(矢印で操作するコマンド)と実体(リンク)が食い違う。 - hover 依存 — サブメニューがマウスでしか開かず、キーボード/タッチで破綻。
- キーボード操作の実装なし — ←→/↑↓/Esc も roving tabindex もない。
- 正しくは
<nav>+<ul>+<a>、 サブメニューは<button aria-expanded>で開閉する。
実装チェックリスト
- 用途がアプリのコマンドであることを確認した(サイトナビには使わない)
- バーは
role="menubar"+aria-label、項目はrole="menuitem" - サブメニューを持つ項目に
aria-haspopup="menu"+aria-expanded - バーは roving tabindex で1タブストップ、←→ で移動
- サブメニューは
role="menu"、項目はtabindex="-1" - ↓/Enter で開き先頭へ、↑↓ で移動、Home/End で端へ
- Esc で閉じて親項目へ戻す、←→ で隣のメニューへ移れる
- hover 依存にせず、フォーカスが見える