{"record":{"id":"442dae84277dc860","repo":"tailwindlabs/headlessui","slug":"headless-ui-cannot-find-ownerdocument-for-conte","errorCode":null,"errorMessage":"[Headless UI]: Cannot find ownerDocument for contextElement: ${contextElement}","messagePattern":"\\[Headless UI\\]: Cannot find ownerDocument for contextElement: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@headlessui-vue/src/components/portal/portal.ts","lineNumber":35,"sourceCode":"  type Ref,\n} from 'vue'\nimport { usePortalRoot } from '../../internal/portal-force-root'\nimport { dom } from '../../utils/dom'\nimport { getOwnerDocument } from '../../utils/owner'\nimport { render } from '../../utils/render'\n\ntype ContextType<T> = T extends InjectionKey<infer V> ? V : never\n\n// ---\n\nfunction getPortalRoot(contextElement?: HTMLElement | null) {\n  let ownerDocument = getOwnerDocument(contextElement)\n  if (!ownerDocument) {\n    if (contextElement === null) {\n      return null\n    }\n\n    throw new Error(\n      `[Headless UI]: Cannot find ownerDocument for contextElement: ${contextElement}`\n    )\n  }\n  let existingRoot = ownerDocument.getElementById('headlessui-portal-root')\n  if (existingRoot) return existingRoot\n\n  let root = ownerDocument.createElement('div')\n  root.setAttribute('id', 'headlessui-portal-root')\n  return ownerDocument.body.appendChild(root)\n}\n\nexport let Portal = defineComponent({\n  name: 'Portal',\n  props: {\n    as: { type: [Object, String], default: 'div' },\n  },\n  setup(props, { slots, attrs }) {\n    let element = ref<HTMLElement | null>(null)","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/tailwindlabs/headlessui/blob/eea57cf46fd6767ed1059012f7073b88eb159fba/packages/@headlessui-vue/src/components/portal/portal.ts#L17-L53","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\n<Portal :target=\"detachedEl\">...</Portal> <!-- detachedEl not in document -->\n\n// after\n<Portal>...</Portal> <!-- let Headless UI resolve the default portal root -->","handlingStrategy":"type-guard","validationCode":"function isAttachedToDocument(el: Element | null): boolean {\n  return el !== null && el.isConnected && el.ownerDocument != null\n}\n\n// only pass the element when it is connected\n<Portal :context-element=\"isAttachedToDocument(el) ? el : undefined\">","typeGuard":"const isConnectedElement = (el: unknown): el is Element & { ownerDocument: Document } => el instanceof Element && el.isConnected && el.ownerDocument != null","tryCatchPattern":"try { getPortalRoot() } catch (e) { if (e instanceof Error && e.message.includes('Cannot find ownerDocument')) { /* fall back to document.body via nextTick */ } else throw e }","preventionTips":["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."],"tags":["headlessui","vue","portal","dom","ssr"],"backgroundTag":"detached-dom-element","analyzedSha":"eea57cf46fd6767ed1059012f7073b88eb159fba","analyzedAt":"2026-08-28T19:22:46.163Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}