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.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.