Focus Trap Implementation — Building a Robust Focus Trap
In this tutorial, you will learn about Focus Trap Implementation. We cover key concepts, practical examples, and best practices to help you master this topic.
A robust focus trap keeps keyboard focus within a container by cycling between first and last focusable elements on Tab and Shift+Tab, essential for modal dialogs.
In this tutorial, you'll build a production-ready Focus Management focus trap.
What You'll Learn
By the end of this lesson, you'll have implemented a complete focus trap that handles dynamic content, nested traps, and edge cases.
Why It Matters
A broken focus trap confuses keyboard users. Either they escape the modal and interact with the background, or they get stuck with no way out.
Real-World Use
Doda Browser's modal system uses this focus trap pattern for all dialogs, side panels, and popups.
Focus Trap Algorithm
flowchart TD
A[Tab pressed] --> B{Shift held?}
B -->|No| C{Active element is last?}
B -->|Yes| D{Active element is first?}
C -->|Yes| E[Focus first element]
C -->|No| F[Normal Tab behavior]
D -->|Yes| G[Focus last element]
D -->|No| H[Normal Shift+Tab]
E --> I[Prevent default]
G --> I
Complete Focus Trap Implementation
class FocusTrap {
constructor(container) {
this.container = container;
this.focusableElements = null;
this.handleKeydown = this.handleKeydown.bind(this);
}
getFocusableElements() {
this.focusableElements = this.container.querySelectorAll(
'a[href], button:not([disabled]), input:not([disabled]), ' +
'textarea:not([disabled]), select:not([disabled]), ' +
'[tabindex]:not([tabindex="-1"]):not([disabled]), ' +
'[contenteditable]'
);
return this.focusableElements;
}
activate() {
this.container.addEventListener('keydown', this.handleKeydown);
const elements = this.getFocusableElements();
if (elements.length > 0) {
elements[0].focus();
} else {
this.container.setAttribute('tabindex', '-1');
this.container.focus();
}
}
deactivate() {
this.container.removeEventListener('keydown', this.handleKeydown);
}
handleKeydown(event) {
if (event.key !== 'Tab') return;
const elements = this.getFocusableElements();
if (elements.length === 0) return;
const firstElement = elements[0];
const lastElement = elements[elements.length - 1];
if (event.shiftKey && document.activeElement === firstElement) {
event.preventDefault();
lastElement.focus();
} else if (!event.shiftKey && document.activeElement === lastElement) {
event.preventDefault();
firstElement.focus();
}
}
}
Using the Focus Trap
const dialog = document.getElementById('settings-dialog');
const trap = new FocusTrap(dialog);
function openDialog() {
dialog.hidden = false;
trap.activate();
}
function closeDialog() {
dialog.hidden = true;
trap.deactivate();
}
Handling Dynamic Content
If focusable elements change after the trap activates, refresh the elements list:
refreshList() {
this.focusableElements = this.getFocusableElements();
}
Common Mistakes
- Not handling Shift+Tab: Focus gets trapped at the first element with no way to go backward.
- Using a stale element list: If elements are added or removed, the trap breaks.
- Not checking for disabled elements: Disabled elements should not be in the focus order.
- Focus trap on elements that are not visible: Hidden focusable elements break the cycle.
- Not deactivating the trap when the dialog closes: The trap persists and blocks all Tab navigation.
Practice and Challenge
1. What does a focus trap do? Keeps keyboard focus within a container by cycling Tab and Shift+Tab.
2. What happens when Tab is pressed on the last focusable element? Focus cycles to the first focusable element.
3. What happens when Shift+Tab is pressed on the first focusable element? Focus cycles to the last focusable element.
4. Why should focusable elements be queried dynamically? Because the DOM may change after the trap is initialized.
5. Challenge: Implement a focus trap and test it with a modal dialog. Verify both Tab and Shift+Tab cycling.
FAQ
Mini Project
Build a focus trap Factory function that creates traps for any container. Test with a modal, a side panel, and a popup menu.
What's Next
Continue to Sentinel Focus to learn about using sentinel elements for focus trapping.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro