本文へ

開発記録

モバイルナビゲーションの開閉とフォーカス:disclosure とモーダルを区別する

ネイティブの details を使うモバイルナビゲーションを例に、リンクの開閉、Escape、外側のクリック、フォーカスの復帰を整理します。通常のナビゲーションを、ページ全体の操作を制限するモーダルと混同しないためのノートです。

林予晴
公開
  • HTML
  • ナビゲーション設計
  • キーボード操作
  • プログレッシブエンハンスメント

モバイル用のメニューを開くと、画面に浮かぶパネルが現れます。ただし、そのパネルを閉じなければページのほかの部分を操作できないとは限りません。この違いによって、Tab でどこへ移動するか、Escape が何をするか、閉じたあとにフォーカスをどこへ戻すかが変わります。

このサイトのモバイルナビゲーションは、disclosure、つまり一つのコントロールでリンクのまとまりを開閉する仕組みです。モーダルではなく、ドキュメントの残りの部分を操作不能にはしていません。2026 年 10 月 26 日に、実際の HTML とイベント処理をたどり、それぞれの役割を分けて確認しました。

ナビゲーションを開くだけなら、ページ全体の操作を制限する必要はありません

メニューの項目は、ノート、プロダクト、作品、協業、About の各ページへのリンクです。読者がするのは移動先を選ぶことであり、ほかの操作を中断して完了しなければならない手続きではありません。

W3C の disclosure navigation の例でも、日常的に「メニュー」と呼ぶものと、WAI-ARIA の menu ロールを宣言するものを区別しています。一般的なサイトナビゲーションには、その複合ウィジェットに求められる一式のキーボード操作は通常必要ありません。[1]

そのため、このサイトではネイティブのリンクと通常の Tab 順序を保ち、menu や menuitem を付けず、フォーカスをパネル内に閉じ込めてもいません。モーダルが悪いのではなく、二つのインタラクションで引き受ける役割が異なります。読者が先に完了またはキャンセルする必要のある対話に変えるなら、背景を操作できるようにするか、フォーカスを制限するかを改めて検討します。

開閉はまずネイティブ要素に任せます

モバイルナビゲーションの外側は details で、開閉の入口は最初の summary です。この組み合わせは HTML 自体に備わる開閉コントロールです。summary は内容のラベルになるので、その中に同じ開閉を行う button をもう一つ置く必要はありません。[2]

ここで使う共通の Button コンポーネントは見た目だけを提供し、実際に出力する要素は summary のままです。パネル内のリンク先も初期 HTML に存在し、開いたあとでスクリプトが作るわけではありません。

そのため、JavaScript がなくても基本的な入口が残ります。ページのスクリプト実行を禁止して About ページを読み込み直し、モバイルナビゲーションの summary にフォーカスを当てて Enter と Space を送りました。Enter で開き、Space で閉じました。使えなくなるのは独自の閉じる演出と追加のイベント処理であり、リンク一式ではありません。

ネイティブの開閉だけで、すべての閉じ方が備わるわけではありません。外側のクリックや、このサイトで設定している Escape の動作は、別途スクリプトを確認する必要があります。details が開くことを、そのままこれらの動作の保証に含めることはできません。

Escape はパネルを消すだけではありません

スクリプトが有効な状態で、開いているナビゲーションから Tab を押すと、最初のノートへのリンクにフォーカスが移りました。続けて Escape を押すとパネルが閉じ、開閉を制御する summary にフォーカスが戻りました。

この復帰には意味があります。パネルを隠すだけで、内部のリンクにあったフォーカスを処理しなければ、読者は次のキーボード操作がどこから始まるか分からなくなる可能性があります。このサイトの Escape 処理は、入口の focus() を明示的に呼び出します。アニメーションが終わったあとに、ブラウザが移動先を推測することには頼っていません。

一方、外側のクリックは別の経路です。モバイルナビゲーションを閉じても、フォーカスを入口へ強制的に戻しません。読者がすでにパネルの外をクリックしたなら、Escape のフォーカス方針を、理由の異なるすべての閉じ方にそのまま当てはめるべきではありません。

また、Tab でナビゲーションの外へ移動したら自動的に閉じる処理もありません。W3C の例にはこの動作が含まれていますが、このサイトはその例を項目ごとに実装したものではありません。[1] パターンを参考にしたからといって、そこにあるすべてのインタラクションを備えているとは言えません。

閉じるアニメーションは追加の演出です

通常モードでは、閉じるときにスクリプトが is-closing を付けます。パネルをフェードアウトさせて少し移動させたあと、details の開いた状態を解除します。コードには、処理を終えるための 200 ミリ秒のタイマーが設定されています。これは設定値であり、測定した操作遅延ではありません。

reduced-motion が有効な場合、閉じる側の分岐はそのまま閉じ、CSS も開くアニメーションを無効にします。ブラウザでこの設定をエミュレートして操作し、パネルが閉じ、閉じる途中の class が残らないことを確認しました。

操作経路ごとに分けると、次のように整理できます。

操作の状況 基本の役割 このサイトでの追加処理
summary を開く ネイティブの disclosure を展開する パネルの表示アニメーション
ナビゲーション内で Escape を押す スクリプトで閉じる summary にフォーカスを戻す
ナビゲーションの外をクリックする スクリプトで閉じる フォーカスを強制的には戻さない
ページのスクリプトを無効にする ネイティブの開閉とリンクを残す 上記の独自イベント処理とトランジションは提供しない

ページを離れるときには、閉じるタイマーと一時的な class も片付けます。これはリソースを解放するためのコード上の処理であり、履歴から戻るあらゆる状況を検証済みだという証拠ではありません。

まず一つの操作経路を最後まで確かめます

モバイルナビゲーションを確認するときは、アイコンが変化するかだけでなく、「入口を見つける、開く、リンクへ移動する、閉じる、フォーカスを確認する」という流れを一通りたどります。そのうえで、外側のクリック、動きを減らす設定、スクリプトの無効化を個別に試すと、各層が何を提供しているかを把握できます。

このローカルの Edge での観察では、幅 390 CSS px の狭いブラウザビューポートを使い、実際にキーボードとマウスで入力しました。実機のスマートフォン、スクリーンリーダー、ブラウザ横断の検証ではありません。読みやすさやパネルの大きさが適切かどうかは、実際の閲覧状況で別途確認する必要があります。

ネイティブ details の一つだけ開く設計、キーボード操作、スクリプトなしの備えでは、経歴の内容をどう開くかを扱いました。モバイルナビゲーションには、移動先とフォーカスの問題が加わります。目的に合うネイティブ要素を選んだあとも、どの理由で閉じたときに何をするかを明確にする必要があります。

参考資料

[1] https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/examples/disclosure-navigation — W3C APG: Example Disclosure Navigation Menu [2] https://html.spec.whatwg.org/multipage/interactive-elements.html — WHATWG HTML: Interactive elements