本文へスキップ
Build

Column

コラム

制作者向け / 更新

ハンバーガーメニューにaria-expandedとフォーカストラップを実装する方法

この記事には広告リンクを含みます。紹介している商品・サービスの一部はアフィリエイトプログラムを利用しています。 商品・サービスの選定はご自身の判断でお願いいたします。

ハンバーガーメニューにaria-expandedとフォーカストラップを実装する方法 のアイキャッチ

コピペで実装したドロワーメニューで、Tabキーを押すとフォーカスがメニュー外に飛んでいく。
この状態はキーボードユーザーにとって「開いたまま外に出られない」状況と同じだ。
原因の大半は aria-expanded・aria-controls の未設定とフォーカストラップの欠落で、上位記事には実装コードが載っていない。
この記事では自分が実案件のjQuery指定環境で組んだコードをベースに、3つの詰まりどころを解決する。

aria-controls と aria-expanded の正しい設定

ハンバーガーメニューの実装で詰まりどころを整理する場面

ハンバーガーボタンには aria-controls と aria-expanded の2属性をセットで書く。
この2つがないと、スクリーンリーダーはボタンが何を制御しているのか、メニューが開いているかどうかを判断できない。

<button
  class="js-drawer-btn"
  aria-controls="drawer-nav"
  aria-expanded="false"
>
  <span class="js-drawer-btn__icon"></span>
</button>
<nav id="drawer-nav">...</nav>
  • aria-controls:ボタンが制御する要素の id を指定する
  • aria-expanded:メニューの開閉状態を true/false で示す。初期値は false

aria-expanded がないと、スクリーンリーダーはボタンをクリックしても何も変化がないように読み上げることがある。
aria-controls がないと、制御対象のナビとボタンの関係がアシスティブテクノロジーに伝わらない。
この2属性で初めて「ボタンがナビを制御している」という文脈がスクリーンリーダーに伝わる。

jQuery で開閉を制御するときは toggleMenu 関数内で attr を切り替える。

function toggleMenu(closeOnly) {
  var isExpanded = $drawerBtn.attr("aria-expanded") === "true";
  if (closeOnly && !isExpanded) return;
  $drawerBtn.attr("aria-expanded", !isExpanded);
  $drawerNav.toggleClass("is-open", !isExpanded);
}

isExpanded で現在の状態を取得し、attr で新しい値をセットする。
toggleClass の第2引数に !isExpanded を渡すと、クラスのon/offを aria-expanded と同期できる。
メニューを開くと aria-expanded="true" に変わり、スクリーンリーダーが「展開済み」と読み上げる。

フォーカストラップと Escape キーの実装

フォーカストラップとは、メニューが開いている間 Tab キーのフォーカスをメニュー内に閉じ込める仕組みだ。
これがないと、Tab キーを数回押すと裏のコンテンツにフォーカスが移り、キーボードユーザーが操作不能になる。

実装の流れは3ステップだ。

  1. オーバーレイ背景(.l-drawer-nav__bg)に tabindex="0" を付与してフォーカス可能にする
  2. そこにフォーカスが移った瞬間、ハンバーガーボタンに戻す
  3. メニュー内 → ボタン → メニュー内 のループが完成し、フォーカスが外に逃げなくなる
<div class="l-drawer-nav__bg" tabindex="0"></div>
$drawerNavBg.on("focus", function () {
  $drawerBtn.focus();
});

自分が関わった案件でこの実装を入れた後、スクリーンリーダーがメニューの開閉状態を正確に読み上げるようになった。
メニューが開いた直後に $drawerBtn.focus() を呼ぶと、スクリーンリーダーが開閉状態を伝えてくれる。
tabindex="0" の1行だけで動く仕組みなので、既存コードへの影響も最小限で済む。

Escape キーの対応はさらにシンプルで、keydown イベント内で1行追加するだけだ。

$(document).keydown(function (e) {
  if (e.key === "Escape") toggleMenu(true);
});

Escape キー対応は実装コストがほぼゼロだが、キーボードユーザーの体験を大きく変える。
上位の解説記事では触れられていないことが多い実装で、ここを押さえるだけで差別化できる。

実装の全体像は CodePen のデモ でも確認できる。

jQuery か Vanilla JS か——実務での判断基準とオーバーレイ背景クリック

指定がない新規案件なら Vanilla JS を選ぶのが基本だ。
jQuery は依存ライブラリが増えるため、すでに読み込まれている環境でのみ使う判断が実務に合っている。

状況

選択

理由

新規案件・指定なし

Vanilla JS

依存追加なし

jQuery 読み込み済み

jQuery

追加コスト0

WordPress デフォルト

jQuery(noConflict)

wp_enqueue_script で制御

WordPress で jQuery を使う場合は、二重読み込みによる「$ is not defined」エラーに注意が必要だ。
詳しい対処は WordPressのjQueryが動かない:二重読み込み診断と修正手順 にまとめてある。

Vanilla JS で書く場合、on("click") は addEventListener("click")。 に置き換えるだけだ。
attr() は setAttribute() に相当し、コード量はほぼ変わらない。

オーバーレイ背景クリックでメニューを閉じる処理は、フォーカス管理とセットで書く必要がある。
toggleMenu(true) だけでなく $drawerBtn.focus() をセットで呼ぶことで、フォーカスが宙に浮かずボタンに戻る。

$drawerNavBg.on("click", function () {
  toggleMenu(true);
  $drawerBtn.focus();
});

背景クリックでメニューを閉じた後、フォーカスがどこにもない状態になると操作が止まる。
click と focus の2行セットで覚えておくとよい。

  • aria-controls と aria-expanded はセットで書き、aria-expanded は開閉時に attr で切り替える
  • オーバーレイ背景に tabindex="0" と focus イベントを組み合わせてフォーカストラップを実現する
  • Escape キーは keydown イベントへの1行追加で完了
  • 新規案件は Vanilla JS が基本。jQuery は読み込み済みの環境でのみ選ぶ
  • 背景クリック時は toggleMenu と focus() を必ずセットで呼ぶ

コーディング代行・実装判断で詰まったら Build に振ってください。
HP制作・保守・改修で悩んでいる方は一度 Build にご相談ください。