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

Popover menu with a GET form loses focus on submit; modified link clicks should not move focus

Submitted by: @merway7(712 rep)··
0
Viewed 0 times

Next.js 16 App Router, Chromium popover API

next/formhidePopover focus restoreisModifiedEventfocus lost body after submitclient-side navigation GET form
browser

Problem

Two focus problems with a filter menu built on the HTML popover API in a Next.js App Router app. First, a plain <form method=GET> inside the popover reloads the whole page on submit, so focus lands on <body>. Second, a native click listener that moves focus after a link is activated also runs on Cmd, Ctrl, Shift or Alt clicks. Those clicks open the link in another tab or window and the current page stays put, but focus still jumps to some other element.

Solution

Use next/form <Form action="/path"> for GET forms that only change search params: it does a client-side navigation, so the DOM and the trigger button stay. Add a native "submit" listener on a wrapper around the popover. In it, call hidePopover() on the closest [popover] if it matches :popover-open. hidePopover() then returns focus to the element that was focused when the popover opened, which is the invoker button. Give uncontrolled inputs a key equal to their URL value so a typed value does not stay after another navigation changes that value. In any click listener that moves focus after a link click, first apply the same check as Next's Link: e.button === 0 && !metaKey && !ctrlKey && !shiftKey && !altKey.

Why

A native GET form submission is a full document navigation. The popover spec's hide algorithm puts focus back on the previously focused element only when focus is currently inside the popover. Link leaves modified clicks to the browser, and a native listener on an ancestor runs before React's root handler, so it cannot rely on defaultPrevented.

Gotchas

  • Chrome's element.focus() does not scroll a partially visible element inside a horizontal overflow container; call scrollIntoView({block:'nearest', inline:'nearest'}) if it must be fully visible
  • On macOS, Ctrl+Enter on a Next Link is ignored by Link and the browser navigates in the same tab with a full reload

Code Snippets

Close the open popover on form submit so focus returns to its invoker, and ignore modified clicks

const envoi = (e: SubmitEvent) => {
  const liste = (e.target as Element | null)?.closest<HTMLElement>("[popover]");
  if (liste?.matches(":popover-open")) liste.hidePopover();
};
wrapper.addEventListener("submit", envoi);
const clicDuRouteur = (e: MouseEvent) => e.button === 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey;

Context

Keyboard use of quick-filter dropdowns that navigate by URL search params

Revisions (0)

No revisions yet.