Finish the modal family with the part that makes it genuinely usable by keyboard and screen-reader users: focus management. When the dialog opens, focus moves into it; while it's open, Tab is trapped so it cycles within the dialog instead of leaking to the page behind; and when it closes, focus returns to the element that opened it. Get these three right and the modal works without a mouse.
// A self-contained component. No props.
function App(): JSX.Element;
The accessible, dismissable dialog from before, now with focus moved in on open, trapped while open, and restored on close.
open: focus jumps from the trigger into the dialog (first control)
Tab at last item: wraps to the first item (stays inside)
Shift+Tab at first: wraps to the last item (stays inside)
close: focus returns to the "Open dialog" trigger
Three responsibilities:
1. save document.activeElement, then focus the first item in the dialog
2. on Tab / Shift+Tab at the edges, wrap focus (preventDefault)
3. on close, restore focus to the saved element
preventDefault and wrap to the other end.The full solution is part of Premium
Walkthrough, edge cases, complexity notes, and the runnable editor unlock with a Premium subscription.
Submissions are part of Premium
Unlock community code, comments, reactions, and framework-specific approaches.
Finish the modal family with the part that makes it genuinely usable by keyboard and screen-reader users: focus management. When the dialog opens, focus moves into it; while it's open, Tab is trapped so it cycles within the dialog instead of leaking to the page behind; and when it closes, focus returns to the element that opened it. Get these three right and the modal works without a mouse.
// A self-contained component. No props.
function App(): JSX.Element;
The accessible, dismissable dialog from before, now with focus moved in on open, trapped while open, and restored on close.
open: focus jumps from the trigger into the dialog (first control)
Tab at last item: wraps to the first item (stays inside)
Shift+Tab at first: wraps to the last item (stays inside)
close: focus returns to the "Open dialog" trigger
Three responsibilities:
1. save document.activeElement, then focus the first item in the dialog
2. on Tab / Shift+Tab at the edges, wrap focus (preventDefault)
3. on close, restore focus to the saved element
preventDefault and wrap to the other end.The full solution is part of Premium
Walkthrough, edge cases, complexity notes, and the runnable editor unlock with a Premium subscription.
Submissions are part of Premium
Unlock community code, comments, reactions, and framework-specific approaches.
Unlock the solution & editor
Runnable editor + tests
Solve in the browser with instant Jest feedback.
Detailed solutions
Walkthroughs, edge cases, and complexity notes.
Multi-framework variants
React, Vue, Vanilla, Angular — same question, different stacks.