patterntypescriptreactModerate
HTML dialog showModal() steals focus from React autoFocus input; set the autofocus attribute instead
Viewed 0 times
React 18+ (StrictMode double effects in dev)
showModalautoFocusautofocus attributeStrictMode double effectsdialog focusing steps
browser
Problem
A React component opens a native <dialog> with showModal() inside a useEffect, and the dialog contains an input with React's autoFocus prop. When the dialog opens, focus ends on the dialog's first focusable element (often the close "×" button) instead of the input. A requestAnimationFrame focus in a parent effect works with a mouse click but fails when the dialog is opened by keyboard (Enter on a menu item) in dev StrictMode, because effects mount, unmount (dialog.close()) and mount again (showModal again).
Solution
Let the browser's dialog focusing steps pick the field. React's autoFocus prop does not write the autofocus HTML attribute on the client: it calls focus() at commit, before the effect that calls showModal(). Set the real attribute with a ref callback, ref={(el) => el?.setAttribute("autofocus", "")}. showModal() then focuses that element on every open, including the second StrictMode open. To select the text, add onFocus={(e) => e.currentTarget.select()}. To check it, trace focusin events and HTMLDialogElement.prototype.showModal calls in the page.
Why
showModal() runs the dialog focusing steps synchronously: the first element with [autofocus], otherwise the first focusable descendant. React's autoFocus calls focus() during commit, which happens before passive effects. In dev StrictMode the effect cleanup closes the dialog, and the next run reopens it, which focuses the first focusable element again.
Code Snippets
Input focused by showModal itself
<input
ref={(el) => el?.setAttribute("autofocus", "")}
name="nom"
defaultValue={label}
onFocus={(e) => e.currentTarget.select()}
/>Context
A rename or edit dialog opened from a menu, with the field to focus on open
Revisions (0)
No revisions yet.