tailwindlabs/headlessui · error · Error
[Headless UI]: Cannot find ownerDocument for contextElement:
Error message
[Headless UI]: Cannot find ownerDocument for contextElement: ${contextElement} What it means
The Vue Portal resolves its mount target by walking from a context element to its ownerDocument. If getOwnerDocument() returns nothing for a non-null contextElement, the element is detached from any document (or is not a real DOM node), so the portal has nowhere to attach and throws this diagnostic error.
Source
Thrown at packages/@headlessui-vue/src/components/portal/portal.ts:35
type Ref,
} from 'vue'
import { usePortalRoot } from '../../internal/portal-force-root'
import { dom } from '../../utils/dom'
import { getOwnerDocument } from '../../utils/owner'
import { render } from '../../utils/render'
type ContextType<T> = T extends InjectionKey<infer V> ? V : never
// ---
function getPortalRoot(contextElement?: HTMLElement | null) {
let ownerDocument = getOwnerDocument(contextElement)
if (!ownerDocument) {
if (contextElement === null) {
return null
}
throw new Error(
`[Headless UI]: Cannot find ownerDocument for contextElement: ${contextElement}`
)
}
let existingRoot = ownerDocument.getElementById('headlessui-portal-root')
if (existingRoot) return existingRoot
let root = ownerDocument.createElement('div')
root.setAttribute('id', 'headlessui-portal-root')
return ownerDocument.body.appendChild(root)
}
export let Portal = defineComponent({
name: 'Portal',
props: {
as: { type: [Object, String], default: 'div' },
},
setup(props, { slots, attrs }) {
let element = ref<HTMLElement | null>(null)View on GitHub (pinned to eea57cf46f)
Solutions
- Ensure the contextElement is attached to the document before the portal resolves (defer with onMounted/nextTick in SSR setups).
- Pass a real, connected DOM element (or null to use the default) rather than a detached or manually created node.
- In SSR, skip portal rendering until client hydration (e.g. wrap in <ClientOnly> in Nuxt).
Example fix
// before <Portal :target="detachedEl">...</Portal> <!-- detachedEl not in document --> // after <Portal>...</Portal> <!-- let Headless UI resolve the default portal root -->
Defensive patterns
Strategy: type-guard
Validate before calling
function isAttachedToDocument(el: Element | null): boolean {
return el !== null && el.isConnected && el.ownerDocument != null
}
// only pass the element when it is connected
<Portal :context-element="isAttachedToDocument(el) ? el : undefined"> Type guard
const isConnectedElement = (el: unknown): el is Element & { ownerDocument: Document } => el instanceof Element && el.isConnected && el.ownerDocument != null Try / catch
try { getPortalRoot() } catch (e) { if (e instanceof Error && e.message.includes('Cannot find ownerDocument')) { /* fall back to document.body via nextTick */ } else throw e } Prevention
- In SSR frameworks (Nuxt), wrap Portal-using overlays in <ClientOnly> or defer mounting to onMounted.
- Pass no contextElement (let the library pick the default root) unless you have a specific, attached element.
- In tests, use a real DOM (jsdom/happy-dom) rather than stubbed element objects.
When it happens
Trigger: Rendering the Portal during SSR or before the element is inserted into the document; passing a contextElement that was created via createElement but never appended; passing a mock/fake element (e.g. jsdom stub or test double) that lacks proper node ownership.
Common situations: Server-side rendering with Nuxt where the portal mounts before hydration; unit tests with fake DOM nodes; teleporting from an element that has been removed from the DOM in the same tick.
Related errors
- Did you forget to passthrough the `ref` to the actual DOM no
- Missing parent
- You forgot to provide an `open` prop to the `Dialog`.
- You provided an `open` prop to the `Dialog`, but the value i
- A <DialogBackdrop /> component is being used, but a <DialogP
AI-assisted analysis of tailwindlabs/headlessui@eea57cf46f (2026-08-28).
Data as JSON: /api/errors/442dae84277dc860.
Report an issue: GitHub.