Focus Traps in React & Vue — Handling Modals and SPAs the Right Way
Focus Traps in React & Vue — Handling Modals and SPAs the Right Way
Introduction
Modern web apps built with React or Vue rely heavily on single-page navigation, modals, dialogs, and overlays. However, without proper focus management, these features can become accessibility barriers — trapping keyboard users inside components or leaving them disoriented after content updates.
Focus trapping ensures users can navigate within an active modal or widget without accidentally moving focus to background content. Once the component closes, focus should return to the triggering element, preserving user context and predictability.
Why Focus Trapping Matters
When interactive layers appear — such as dialogs, dropdowns, or menus — keyboard users should:
- Have focus automatically moved into the new layer.
- Be able to navigate within that layer using Tab and Shift + Tab.
- Escape the layer easily using Esc or another closing mechanism.
- Return focus to their previous position when the layer closes.
These requirements align with WCAG 2.2 criteria 2.1.2 (No Keyboard Trap) and 2.4.3 (Focus Order), ensuring uninterrupted operation for keyboard and assistive technology users.
Handling Focus in React Modals
React’s component-based architecture makes it straightforward to implement focus trapping with hooks or third-party libraries.
Example: Using react-focus-lock
import React, { useState } from "react";
import FocusLock from "react-focus-lock";
function Modal() {
const [isOpen, setOpen] = useState(false);
return (
<>
<button onClick={() => setOpen(true)}>Open Modal</button>
{isOpen && (
<FocusLock>
<div role="dialog" aria-modal="true">
<h2>Example Modal</h2>
<button onClick={() => setOpen(false)}>Close</button>
</div>
</FocusLock>
)}
</>
);
}
FocusLock automatically confines Tab navigation inside the modal container until the dialog is closed, fulfilling WCAG’s "no keyboard trap" requirement.
Managing Focus in Vue Modals
In Vue, similar functionality can be implemented using built-in lifecycle hooks or libraries such as focus-trap-vue.
Example: Using focus-trap-vue
<template>
<div>
<button @click="show = true">Open Dialog</button>
<FocusTrap v-if="show">
<div role="dialog" aria-modal="true" class="modal">
<h2>Dialog Example</h2>
<button @click="closeDialog">Close</button>
</div>
</FocusTrap>
</div>
</template>
<script setup>
import { ref } from "vue";
import { FocusTrap } from "focus-trap-vue";
const show = ref(false);
const closeDialog = () => (show.value = false);
</script>
This library automatically focuses the first tabbable element when the dialog opens and restores focus when it closes.
Focus Restoration: Returning to the Trigger
When a modal closes, focus must return to the control that originally opened it. This provides a continuous, predictable experience for keyboard users. In React or Vue, this can be handled manually using refs:
const buttonRef = useRef();
function closeModal() {
setOpen(false);
buttonRef.current.focus(); // return focus to trigger
}
Best Practices for Focus Trap Implementation
- Set
aria-modal="true"androle="dialog"on modal containers. - Ensure only interactive elements inside the modal are tabbable while it's open.
- Disable background elements visually and semantically (e.g., using
aria-hidden="true"). - Provide a visible focus outline for clarity and compliance with WCAG 2.2 Criterion 2.4.7 (Focus Visible).
- Support Esc key to close dialogs, ensuring an easy exit path.
Common Focus Trap Mistakes
- Not restoring focus: Users lose context when focus doesn’t return to the open trigger.
- Blocking all keystrokes: Some scripts incorrectly prevent arrow keys or shortcuts.
- Nested traps: Overlapping modals can confuse navigation; trap one region at a time.
Testing Focus Management
To test focus traps effectively:
- Open the modal using only the keyboard.
- Press Tab repeatedly to ensure focus cycles only within the dialog.
- Press Shift + Tab to confirm backward focus order.
- Press Esc to close and check that focus returns to the trigger.
Validation tools like axe and browser DevTools Accessibility panels can help flag focus management errors.
Conclusion
Accessible modals and overlays rely on precise focus management. By implementing focus traps properly in React or Vue, you prevent keyboard traps, maintain consistent navigation, and ensure an inclusive experience for all users — whether they use a mouse, keyboard, or assistive technology.
Next Steps: Audit your modals and dialogs for focus trapping issues, implement libraries like react-focus-lock or focus-trap-vue, and test them across browsers and screen readers to meet WCAG 2.2 standards.
