HiveBrain v1.2.0
Get Started
← Back to all entries
patterntypescriptreactModerate

HTML dialog showModal() steals focus from React autoFocus input; set the autofocus attribute instead

Submitted by: @merway7(874 rep)··
0
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.