primitives icon indicating copy to clipboard operation
primitives copied to clipboard

Can't select an option rendered in a portal in a Dialog modal=true

Open crissadriana opened this issue 1 year ago • 8 comments

I'm using the Dialog primitive to build a Drawer component and inside the Dialog / Drawer I need to use a headlessui combobox which has some options to select rendered in a portal (hence outside the radix Dialog root) but when trying to select any option from the combobox won't let me do that because the Dialog is a modal and it prevents clicking outside the Dialog (unless is the overlay).

If I set modal= false I can selected the wanted options from the portal but I lose the Dialog Overlay. Can someone advise if there's a way to have both the Dialog modal= true AND the option to interact with elements in a portal outside of the Dialog?

this is the version used for radix ui Dialog: "1.1.1"

image

crissadriana avatar Sep 12 '24 11:09 crissadriana

Same here. Does anyone have a good idea?

Demi1024 avatar Sep 13 '24 07:09 Demi1024

Same here

agrajak avatar Oct 27 '24 02:10 agrajak

Unfortunately experiencing the same issue here while migrating from HeadlessUI to Radix

EmileKost avatar Nov 14 '24 14:11 EmileKost

Same issue here.

For now I switched to using the prop modal set to false and rendering a manual backdrop like below. But just tried if it works in general. It's possible that it might cause some issues I am not aware of right now.

export const Dialog = ({ children, ...props }: DialogPrimitive.DialogProps) => {
  return (
    <DialogPrimitive.Root modal={false} {...props}>
      {props.open ? (
        <div className="fixed inset-0 z-50 grid items-center overflow-y-auto bg-black/30 data-[state=open]:animate-overlayShow" />
      ) : null}
      {children}
    </DialogPrimitive.Root>
  )
}``` 

nik-lampe avatar Nov 21 '24 16:11 nik-lampe

Would love to know what the real answer is to this issue. There appears to be no way to manage the stacking of portals across libraries. (i.e. Radix / Headlessui)

I'm deferring to nik-lampe's answer.

StephenSHorton avatar Nov 21 '24 20:11 StephenSHorton

The problem is that both the Radix Modal and HeadlessUI Combobox are rendered in two separate portals, where they don't know from each other which state they are in. If you use anchore={} in your Combobox this will cause the issue that you can't click it anymore. You can absolutely position it relative to the modal it is in. You can also use useFloating (which is used by headless under the hood), and assign this to the container of the Combobox: const { refs, floatingStyles } = useFloating({}) and then on the container: ref={refs.setFloating} style={floatingStyles}.

Now you should be able to use the Radix dialog as a Modal, and still use the HeadlessUI Combobox inside of it. So your Combobox will look something like this:

<ComboboxInput id={id} className="w-full border-none py-2 pl-2.5 pr-10 text-sm leading-5 text-gray-900 focus:ring-0" onChange={event => setQuery(event.target.value)} placeholder={placeholder} ref={refs.setReference} /> <ComboboxOptions className="!max-h-80 mt-2 h-fit z-30 w-[var(--input-width)] overflow-y-auto overflow-x-hidden rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" ref={refs.setFloating} style={floatingStyles} modal={false} > ... Rest of content of Combobox

I hope this helps!

EmileKost avatar Nov 22 '24 07:11 EmileKost

I just fixed this same issue on my app. The solution that worked for me combines a few approaches:

  1. Set modal={false} on the Dialog component as @nik-lampe suggested
  2. Add a manual backdrop when the dialog is open for visual effect
  3. Fix focus management within the dialog/drawer

Here's the implementation that successfully allows interaction with portaled content while maintaining the visual experience of a modal:

// Your Dialog/Drawer component
<Dialog 
  open={open} 
  onOpenChange={onOpenChange}
  modal={false} // This is crucial - allows focus to reach portaled content
  {...props}
>
  {/* Render a manual backdrop when open */}
  {open && (
    <div className="fixed inset-0 z-40 bg-black/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0" />
  )}
  <DialogContent 
    className={className}
    // Don't use these handlers as they can block focus
    // onOpenAutoFocus={(e) => e.preventDefault()}
    // onCloseAutoFocus={(e) => e.preventDefault()}
    // onPointerDownOutside={(e) => isEditing && e.preventDefault()}
    // onInteractOutside={(e) => isEditing && e.preventDefault()}
  >
    {/* Content with your headlessUI combobox */}
  </DialogContent>
</Dialog>

I also found that changing any onPointerDown event handlers to onClick with simple e.stopPropagation() (instead of e.preventDefault()) helped ensure proper focus management for inputs and select elements:

<input
  value={value}
  onChange={(e) => handleChange(e.target.value)}
  onClick={(e) => e.stopPropagation()} // Better than onPointerDown with preventDefault
  className="your-class"
/>

This combination fixed all our focus issues

richardsondx avatar Feb 25 '25 21:02 richardsondx

@EmileKost 's approach (https://github.com/radix-ui/primitives/issues/3119#issuecomment-2493070115) worked for me. The issue of the esc key remains though, and I can't find a solution.

It's the first time I got familiar with the floating-ui library. Here's a more elaborate usage of the useFloating hook that I used to apply some styles:

  const { refs, floatingStyles } = useFloating({
    placement: 'bottom',
    middleware: [
      flip(),
      size({
        apply({ availableHeight, elements }) {
          const value = `${Math.max(0, availableHeight)}px`
          elements.floating.style.setProperty(
            '--available-height',
            value,
          )
        },
      }),
    ],
    whileElementsMounted: autoUpdate,
  })

doiali avatar May 22 '25 23:05 doiali

The escape key issue seems to be because the Radix modal context is miscounting against data-scroll-locked located on the body. If you have a select field open and then hit escape, it seems to miss removing the point-events: none without fail.

Liam-Twinkl avatar Aug 25 '25 17:08 Liam-Twinkl

Issue being spoken about specifically here

https://github.com/radix-ui/primitives/issues/3141#issuecomment-2458092407

Liam-Twinkl avatar Aug 25 '25 17:08 Liam-Twinkl