モーダル内の「迷子」を救え:インライン要素を跨ぐフォーカストラップの実装術
フロントエンド開発の現場で、モーダルやドロップダウンを実装した際、「Tabキーを押すとフォーカスがモーダルの外へ突き抜けてしまう」という問題に直面したことはないだろうか?
一見些細なUXの欠陥に見えるが、これはスクリーンリーダーを使っているユーザーや、キーボード操作が必須のユーザーにとっては「行き止まりのない迷路」に閉じ込められるに等しい、重大なアクセシビリティの欠陥だ。
今回は、特にインライン要素が混在する複雑なモーダル内でも、確実にフォーカスを捕縛(トラップ)する実装パターンについて、ブラウザの挙動という「深層」に触れながら解説しよう。
—
ブラウザはどうやってフォーカスを追いかけているのか
そもそも、ブラウザは「次にどこにフォーカスを当てるか」を、DOMツリーの順序と各要素の `tabindex` 属性、そして「インタラクティブであるか」という内部フラグに基づいて判定している。
フォーカストラップの核心は、「フォーカスがモーダルの最後の要素に達した際、強制的に最初の要素へワープさせる」という監視ループを回すことにある。
—
実践:現場で使える「フォーカストラップ」のベストプラクティス
多くのライブラリがこの処理を抽象化しているが、要件に合わせて自作できるようになると、ライブラリの依存関係を最小限に抑えられる。以下に、可読性と堅牢性を両立させた実装を示す。
/
- モーダル内のフォーカスをトラップする関数
- @param {HTMLElement} modal – モーダルコンテナ
/
function trapFocus(modal) {
// フォーカス可能な要素をすべて取得(インライン要素のtabindex=”0″も含む)
const focusableElements = modal.querySelectorAll(
‘a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex=”-1″])’
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length – 1];
modal.addEventListener(‘keydown’, (e) => {
const isTabPressed = e.key === ‘Tab’ || e.keyCode === 9;
if (!isTabPressed) return;
if (e.shiftKey) {
// Shift + Tab の場合:最初の要素なら最後へ
if (document.activeElement === firstElement) {
lastElement.focus();
e.preventDefault();
}
} else {
// Tab の場合:最後の要素なら最初へ
if (document.activeElement === lastElement) {
firstElement.focus();
e.preventDefault();
}
}
});
}
—
なぜこれが「現場の正解」なのか
このコードの肝は、`querySelectorAll` で取得するセレクタの網羅性にある。
- `a[href]` は当然として、`[tabindex]:not([tabindex=”-1″])` を含めることで、`` や `
` をインタラクティブに使っているような変則的なモーダルにも対応できる。
- `e.shiftKey` を判定することで、逆方向への移動(Tabキーの逆行)にも完全対応している。
注意すべき「落とし穴」
現場でよくある失敗は、モーダルが開いた瞬間にフォーカスを移動させる処理を忘れることだ。モーダルを開くトリガーとなったボタンからフォーカスが外れたままでは、ユーザーは「どこから操作を始めればいいか」を迷ってしまう。
必ず、モーダル表示直後に `firstElement.focus()` を叩くことを忘れないでほしい。
—
今後の展望:ネイティブの力「Focus Management」
正直に言おう。現代のフロントエンド開発では、将来的にはこの手のスクリプトは不要になる可能性がある。W3Cで策定が進んでいる `
しかし、デザインの要件で複雑なCSSレイアウトが必要だったり、既存のレガシーなUIライブラリを改修する場合、今回紹介したJSベースの制御は依然として最強の武器になる。
「ブラウザの挙動をハックする」のではなく、「ブラウザの挙動を理解して、アクセシブルな道筋を作る」。それこそが、シニアエンジニアが持つべき矜持だ。ぜひ次のスプリントで、このロジックを自身のコンポーネントに組み込んでみてほしい。きっと、ユーザーからの見えない感謝を感じられるはずだ。

コメント