Portal
Headless Portal; covers HTML Teleport, document portal.
| Package | @vizejs/ui/portal |
| Maturity | stable |
| Own the source | vize lib pull portal |
| Requires | — |
| Aliases | teleport, portalled content, nested portal stack |
| Covers | HTML Teleport, document portal |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { Portal } from "@vizejs/ui/portal";
Or copy the source into your project with vize lib pull portal (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Portal that moves a labelled notice to the document body after hydration. -->
<script setup lang="ts">
import { useId } from "vue";
import { Portal } from "@vizejs/ui/portal";
const titleId = useId();
</script>
<template>
<Portal>
<aside :aria-labelledby="titleId">
<h2 :id="titleId">Scheduled maintenance</h2>
<p>The dashboard will be read-only on Saturday from 02:00 to 04:00 UTC.</p>
</aside>
</Portal>
</template>
API
Portal
Source: src/families/overlays/portal/portal.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
to |
string | HTMLElement |
"body" |
CSS selector or element the content is moved into. |
disabled |
boolean |
false |
Render in place instead of moving the content. |
defer |
boolean |
true |
Keep content in place until the target exists, avoiding SSR mismatch. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
— | Portalled contents. |
Exposed
| Member | Type | Description |
|---|---|---|
depth |
— | |
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
Behavior
Normative state × input → outcome table for portal.vue and portal-stack.ts
(@vizejs/ui/portal). Every row is proven by the named test in
src/families/overlays/portal/portal.test.ts,
src/families/overlays/portal/portal-stack.test.ts, or
src/families/overlays/portal/portal-ssr.test.ts; compile-only assertions
live in src/families/overlays/portal/portal.types.test-d.ts.
| # | State | Input | Outcome | Proven by |
|---|---|---|---|---|
| T1 | any | render | slotted content stays queryable | renders slotted content |
| T2 | hydrated | default target | content moves to document.body |
moves content into the document body |
| T3 | hydrated | disabled |
content stays in place | keeps content in place when disabled |
| T4 | any | render | exposed element is the rendered node |
exposes the rendered element for composition |
| T5 | SSR | default render | byte-identical in-place markup, no body relocation | SSR test |
| T6 | nested | portal inside portal | layers publish data-vize-portal-depth 0, 1, … |
publishes incrementing depth for nested portals |
| T7 | mounted | stack query | shared stack orders layers shallow-to-deep, top last | tracks nested layers shallow-to-deep in the shared stack |
| T8 | unmounting | layer leaves | released layers drop off the stack immediately | releases layers from the stack on unmount |
| T9 | SSR | nested render | deterministic depth markup, stack stays untouched | renders deterministic nesting depth without touching the stack |
| T10 | public types | mutating stack entry | compilation rejects misuse | src/families/overlays/portal/portal.types.test-d.ts |