headlessui icon indicating copy to clipboard operation
headlessui copied to clipboard

Unexpected keyboard focus behaviour on Popover with portalled panel

Open lenzls opened this issue 3 years ago • 0 comments

What package within Headless UI are you using?

@headlessui/react

What version of that package are you using?

v1.6.6

What browser are you using?

Tested in Firefox and Chromium

Reproduction URL https://codepen.io/simondl/pen/ZEoWQBq?editors=1010

Describe your issue I'm using the Popover component and the Popover.Panel is portalled to a different DOM node.

I expect that when the keyboard focus moves out of the Popover.Panel, it is placed back on the Popover.Panel. Instead, it is being placed on the sibling element of the Popover.Panel in the portalled location.

The linked codepen shows the same popover component in two different DOM structure. And shows a small reproduction recipe. Case (1) shows exactly my error case. Case (2) shows a different DOM structure, where the focus is handled to my expectation.

Potential cause The focus-sentinels are only rendered when isPortalled is true (see popover.tsx#L753).

In my case, isPortalled is false. Therefore, the focus-sentinels are not rendered and there is no special focus handling.

The reason is, that the isPortalled check only returns true if button and panel are descendants of different 'root' elements.

    for (let root of document.querySelectorAll('body > *')) {
      if (Number(root?.contains(button)) ^ Number(root?.contains(panel))) {
        return true
      }
    }

taken from popover.tsx#L219

I believe this check is flawed. In my case, button and panel are part of the same 'root' element, but within that in very different locations.

I believe a better condition for isPortalled might be to check if Button and Panel are direct siblings. That way, the focus-sentinels would render whenever the native browser focus management would not suffice.

lenzls avatar Sep 09 '22 09:09 lenzls