From 44d0f7a25760a8a13de91816570f6fbdf9118085 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Wed, 24 Dec 2025 10:34:22 -0500 Subject: [PATCH] Add focus trapping for modal dialogs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Focus moves to first focusable element when modal opens - Tab/Shift+Tab cycles within modal only - Escape key closes modal - Focus returns to previously focused element on close 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- index.html | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index 58ee1a2..25fd04a 100644 --- a/index.html +++ b/index.html @@ -1142,12 +1142,64 @@ } } + let previouslyFocusedElement = null; + let activeModalId = null; + function openModal(id) { - document.getElementById(id).classList.add('active'); + previouslyFocusedElement = document.activeElement; + activeModalId = id; + const modal = document.getElementById(id); + modal.classList.add('active'); + + // Focus first focusable element in modal + const focusable = modal.querySelectorAll('button, input, select, textarea, [tabindex]:not([tabindex="-1"])'); + if (focusable.length > 0) { + setTimeout(() => focusable[0].focus(), 50); + } + + // Add event listeners for focus trap and Escape + document.addEventListener('keydown', handleModalKeydown); } function closeModal(id) { document.getElementById(id).classList.remove('active'); + document.removeEventListener('keydown', handleModalKeydown); + activeModalId = null; + + // Restore focus to previously focused element + if (previouslyFocusedElement) { + previouslyFocusedElement.focus(); + previouslyFocusedElement = null; + } + } + + function handleModalKeydown(e) { + if (!activeModalId) return; + const modal = document.getElementById(activeModalId); + + // Close on Escape + if (e.key === 'Escape') { + e.preventDefault(); + closeModal(activeModalId); + return; + } + + // Trap focus on Tab + if (e.key === 'Tab') { + const focusable = modal.querySelectorAll('button, input, select, textarea, [tabindex]:not([tabindex="-1"])'); + if (focusable.length === 0) return; + + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); + last.focus(); + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); + first.focus(); + } + } } async function verifyPin() {