primitives
primitives copied to clipboard
Can't select an option rendered in a portal in a Dialog modal=true
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"
Same here. Does anyone have a good idea?
Same here
Unfortunately experiencing the same issue here while migrating from HeadlessUI to Radix
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>
)
}```
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.
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!
I just fixed this same issue on my app. The solution that worked for me combines a few approaches:
- Set
modal={false}on the Dialog component as @nik-lampe suggested - Add a manual backdrop when the dialog is open for visual effect
- 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
@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,
})
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.
Issue being spoken about specifically here
https://github.com/radix-ui/primitives/issues/3141#issuecomment-2458092407