Add support for custom portal container in floating components (Dialog, Dropdown, Popover, etc.) #3698
Replies: 3 comments
-
Came here to post this one to. While there are 99% of case where you want portal on body, there are some cases, specifically due to design compromise, where it is necessary to display a drawer for exemple into a container and this is almost impossible or highly inconvenient to do in css. Having the ability to define a container for the portal in those rare case would be a big win. EDIT : #666 |
Beta Was this translation helpful? Give feedback.
-
Thanks for your input, @Crocsx — totally agree on the value of supporting custom portal containers. Unfortunately, it seems like Headless UI isn’t actively maintained anymore, or at least issues like this don’t get much attention. That’s been my experience so far, especially working on browser extensions where Shadow DOM and strict container control are essential. In contrast, libraries like shadcn/ui handle these use cases much better by exposing portal container props out of the box. For anyone building extensions or apps in non-standard environments, I’d honestly recommend exploring alternatives — Headless UI just doesn’t offer the flexibility or responsiveness needed in those scenarios. Still hoping the maintainers take another look at this, but in the meantime, shadcn/ui has been a far smoother experience. |
Beta Was this translation helpful? Give feedback.
-
For anyone facing this issue, I’ll share the solution I came up with—I hope it helps. I solved this problem in the following way: my project was a monorepo with a shared UI package (built on top of Headless UI) that’s used across multiple web applications and a browser extension. For the extension, the components needed to render inside the Shadow DOM, but the main problem was that none of the existing portal implementations allowed rendering the portal content inside the shadow root. I tried different approaches, but none of them worked out. Eventually, I decided to implement the portal part myself using Floating UI, while still relying on Headless UI for accessibility. I packaged this solution and have been running it in production without issues for a while now. import React, { ReactNode, useMemo, useRef } from 'react';
import { Icon } from '@dataak/icons';
import { isExtension } from '@dataak/utils';
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react';
import ReactUniPopper from 'react-uni-popper';
import { twMerge } from 'tailwind-merge';
import useDOMRef from './useDOMRef';
interface DropDownProps {
children: ReactNode;
className?: string;
toggler?: ReactNode;
width?: number;
}
function DropDown({ children, width, toggler, className }: DropDownProps) {
const targetRef = useRef<HTMLButtonElement>(null);
const { suiviRef } = useDOMRef();
const rootRef = useMemo(() => {
if (isExtension()) {
return (
suiviRef?.current?.querySelector('#suivi-extension-container') ||
document.body
);
}
return document.body;
}, [suiviRef]);
return (
<Menu>
{({ open }) => (
<>
<MenuButton ref={targetRef}>
{!toggler ? (
<Icon icon='DotsVertical' width={20} height={20} />
) : (
toggler
)}
</MenuButton>
{open && (
<ReactUniPopper
reference={targetRef.current}
placement='bottom'
portalContainer={rootRef as HTMLElement}
zIndex={1300}
>
<MenuItems
transition
className={twMerge(
'rounded-lg border p-0 px-2 py-1 shadow-lg [--anchor-gap:4px] w-60 border-gray-200 bg-white',
className
)}
>
{React.Children.toArray(children)
.filter(React.isValidElement)
.map((child) => (
<MenuItem>{child}</MenuItem>
))}
</MenuItems>
</ReactUniPopper>
)}
</>
)}
</Menu>
);
}
export default DropDown;
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi, First of all, thank you for your amazing work on Headless UI.
I'm working on a browser extension project where the app is rendered inside a shadow DOM.
In this context, it's important to have control over where portals are mounted.
Currently, libraries like shadcn/ui and Hero UI allow passing a custom container to their portal components (e.g., Dialogs, Dropdowns, Popovers, Listboxes).
However, Headless UI's floating components currently hardcode the portal target (typically document.body) without exposing a way to override it.
Since Headless UI is meant to be "headless", it would be great to allow developers to specify a custom container for portals.
This would make the library even more flexible and suitable for advanced use cases like Shadow DOM.
Thank you for considering this feature request!
Beta Was this translation helpful? Give feedback.
All reactions