We use cookies for site analytics. Accept to help us understand how the site is used. See our Privacy Policy for details.
A medium frontend problem, graded against 6 test cases (3 of them hidden).
React and browser-platform problems - hooks, event handling, and rendering behaviour.
Reach for it when you see: A component, hook, or DOM-behaviour question rather than an algorithm.
More Frontendproblems →```tsx
const FOCUSABLE = 'a[href],button,textarea,input,select,[tabindex]:not([tabindex="-1"])';
export function Modal({ open, onClose, children }: { open: boolean; onClose: () => void; children: React.ReactNode }) {
const ref = useRef<HTMLDivElement>(null);
const previouslyFocused = useRef<HTMLElement | null>(null);
useEffect(() => {
if (!open) return;
previouslyFocused.current = document.activeElement as HTMLElement;
const focusables = ref.current?.querySelectorAll<HTMLElement>(FOCUSABLE);
focusables?.[0]?.focus();
function onKey(e: KeyboardEvent) {
if (e.key === "Escape") { onClose(); return; }
if (e.key !== "Tab") return;
const list = Array.from(ref.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? []);
if (list.length === 0) { e.preventDefault(); return; }
const idx = list.indexOf(document.activeElement as HTMLElement);
const next = e.shiftKey
? (idx <= 0 ? list.length - 1 : idx - 1)
: (idx >= list.length - 1 ? 0 : idx + 1);
e.preventDefault();
list[next].focus();
}
document.addEventListener("keydown", onKey);
return () => {
document.removeEventListener("keydown", onKey);
previouslyFocused.current?.focus();
};
}, [open, onClose]);
if (!open) return null;
return createPortal(
<div role="dialog" aria-modal="true" ref={ref}>{children}</div>,
document.body
);
}
```
Required a11y behaviors for modals:
- `role="dialog"` + `aria-modal="true"`.
- Focus moves into the modal on open; restores to the previously-focused element on close.
- Tab is trapped (the math you implemented).
- Escape closes.
- Backdrop click closes (optional, but standard).
- Render via portal so the DOM hierarchy doesn't trap focus inside an inert ancestor.
Modern alternative: the native `<dialog>` element handles focus trap, scroll lock, and inertness automatically. Use `dialog.showModal()`. Browser support is now broad.
The full reference solution in every supported language stays in the editor above - reveal it there once you have had a real attempt.
Read off this problem's own test suite, so these are the cases a submission actually has to survive.
These apply to the pattern as a whole, not just this problem.