Your popup escaped the Shadow DOM. And the obvious fix broke the host page.
We ship a portal menu as a UMD web component embedded in JSF apps. FloatingPortal-based components (Tooltip, Select, Autocomplete, PhoneInput) render into document.body by default, which is outside the shadow root, so unstyled.
The workaround was injecting a global stylesheet. Side effect: the whole Tailwind preflight leaked onto the host page and clobbered PrimeFaces and Bootstrap inputs.
The fix is an optional PortalProvider. Each component takes a root prop; if omitted it uses the nearest provider's root; with no provider it falls back to document.body. Nothing changes for anyone who doesn't opt in.
export const useResolvedPortalRoot = (root?: PortalRoot) => {
const portalRoot = usePortalRoot();
// undefined = herdar do provider; null = opt-out explícito
return root === undefined ? portalRoot : root;
};
The detail that matters most: root={null} has to mean "ignore the provider". With root ?? portalRoot, null collapses into the fallback and the escape hatch is gone. So undefined and null behave differently on purpose.
I left Uploader/Modal out on purpose. They still use createPortal, no consumer asked for it, and the fix pattern is different.
Have you ever had to decide whether null means "I don't know" or "I don't want it" in a component API?
Top comments (0)