ナビゲーション

メニューボタン

解説あり

Menu Button

押すとメニューを開くボタン。aria-haspopup と aria-expanded で関係を伝えます。

メニューボタン(Menu Button)とは?

メニューボタンは、押すと操作の一覧(メニュー)がポップアップするボタンです。 「⋯」や「操作 ▾」のようなボタンを押すと「複製・名前を変更・削除」などが出てくる、あれです。 アプリ的なコマンドの集まりを、場所を取らずに提供できます。

補足

ここでいう「メニュー」はアプリのコマンド用です。サイト内を移動するための ナビゲーションには通常 role="menu" を使わず、<nav> + リンクのリストにします(詳しくはメニューバーのページを参照)。

なぜアクセシビリティが大事なの?

ライブデモ(推奨実装)

下のメニューボタンは APG に沿った実装です。マウスを使わず操作してみてください。

アクセシブルなメニューボタン

試してみよう:Tab でボタンへ → Enter / Space / ↓ で開く(先頭にフォーカス)→ ↑ ↓ で項目移動、Home / End で端へ → Enter で実行 → Esc で閉じてボタンに戻る。

ポイント

開閉時のフォーカス移動がポイントです。開いたら最初の項目へEsc で閉じたらボタンへフォーカスを戻すことで、 キーボード利用者が「今どこにいるか」を見失いません。

キーボード操作

キー動作必須/任意
Enter / Space / (ボタン上)メニューを開き、最初の項目へフォーカス必須
(ボタン上)メニューを開き、最後の項目へフォーカス任意
/ (メニュー内)前 / 次の項目へフォーカス移動必須
Home / End最初 / 最後の項目へ移動任意(推奨)
Enter / Space(項目上)項目を実行し、メニューを閉じてボタンへ戻る必須
Escメニューを閉じてボタンへフォーカスを戻す必須

必要な WAI-ARIA / ロール

付ける場所属性 / ロール意味
トリガー<button> + aria-haspopup="menu"押すとメニューが開くボタンであることを伝える。
トリガーaria-expanded="true | false"メニューが開いているか。開閉に合わせ必ず更新する。
トリガーaria-controls="メニューのid"どのメニューを開くボタンかを示す。
メニューの箱role="menu" + aria-labelledbyメニューであることと、トリガーを名前として持つことを伝える。
各項目role="menuitem" + tabindex="-1"メニュー項目であることを伝える。フォーカスは JS で管理(roving)。
閉じたメニューhidden閉じている間は読み上げ/操作の対象外にする。

実装:推奨パターン(Good)

良い例 / 推奨

トリガーは <button aria-haspopup="menu" aria-expanded>、メニューはrole="menu" + role="menuitem"。開閉時のフォーカス移動Esc 復帰を JS で実装します。

マークアップ:

<div class="menu">
  <button type="button"
          id="menu-button-trigger"
          aria-haspopup="menu"
          aria-expanded="false"
          aria-controls="menu-button-list">
    操作 ▾
  </button>

  <ul id="menu-button-list"
      role="menu"
      aria-labelledby="menu-button-trigger"
      hidden>
    <li role="menuitem" tabindex="-1">複製</li>
    <li role="menuitem" tabindex="-1">名前を変更</li>
    <li role="menuitem" tabindex="-1">削除</li>
  </ul>
</div>

開閉・フォーカス管理のスクリプト:

const trigger = document.getElementById('menu-button-trigger');
const menu = document.getElementById('menu-button-list');
const items = Array.from(menu.querySelectorAll('[role="menuitem"]'));

function open(focusIndex) {
  menu.hidden = false;
  trigger.setAttribute('aria-expanded', 'true');
  items[focusIndex].focus(); // 開いたらメニュー内へフォーカスを移す
}
function close(returnFocus = true) {
  menu.hidden = true;
  trigger.setAttribute('aria-expanded', 'false');
  if (returnFocus) trigger.focus(); // Esc 等ではトリガーへ戻す
}

trigger.addEventListener('click', () =>
  menu.hidden ? open(0) : close());
trigger.addEventListener('keydown', (e) => {
  if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') {
    e.preventDefault(); open(0);
  } else if (e.key === 'ArrowUp') {
    e.preventDefault(); open(items.length - 1);
  }
});

items.forEach((item, i) => {
  item.addEventListener('keydown', (e) => {
    if (e.key === 'ArrowDown') { e.preventDefault(); items[(i + 1) % items.length].focus(); }
    else if (e.key === 'ArrowUp') { e.preventDefault(); items[(i - 1 + items.length) % items.length].focus(); }
    else if (e.key === 'Home') { e.preventDefault(); items[0].focus(); }
    else if (e.key === 'End') { e.preventDefault(); items[items.length - 1].focus(); }
    else if (e.key === 'Escape') { close(); }
    else if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); run(item); }
  });
  item.addEventListener('click', () => run(item));
});

function run(item) { /* 実行処理 */ close(); }

// メニューの外をクリックしたら閉じる
document.addEventListener('click', (e) => {
  if (!menu.hidden && !e.target.closest('.menu')) close(false);
});

アンチパターン(Bad)

下は :hover だけで開くドロップダウンです。マウスを乗せれば開きますが、キーボードでは開くことすらできません。

hover だけで開くドロップダウン

試してみよう:マウスを乗せると開きますが、Tab でトリガーにフォーカスできず、↓ でも Esc でも操作できません。マウスを外すと即閉じます。

<!-- ❌ アンチパターン:hover だけで開くドロップダウン -->
<div class="dropdown">
  <div class="trigger">操作 ▾</div>
  <!-- マウスを乗せている間だけ CSS で表示。aria もキーボードもない -->
  <ul class="menu-list">
    <li onclick="run('copy')">複製</li>
    <li onclick="run('rename')">名前を変更</li>
    <li onclick="run('delete')">削除</li>
  </ul>
</div>

<style>
  .menu-list { display: none; }
  .dropdown:hover .menu-list { display: block; } /* hover 依存 */
</style>

悪い例 / 避ける

この実装の問題点:

  • キーボードで開けないdiv トリガーはフォーカス不可。/Enter も効かない。
  • hover 依存 — マウスが使えない人・タッチ環境で破綻し、マウスを外すと閉じてしまう。
  • 役割と状態が伝わらないaria-haspopup/aria-expanded/role="menu" がない。
  • Esc で閉じられない・フォーカス管理がない — どこにいるか見失う。

実装チェックリスト


原文(英語):Menu Button Pattern — W3C APG(新しいタブで開きます)