Skip to main content
The <Teleport> component allows you to render part of a component’s template in a DOM node that exists outside the component’s DOM hierarchy.

Basic Usage

<template>
  <button @click="open = true">Open Modal</button>

  <Teleport to="body">
    <div v-if="open" class="modal">
      <p>Hello from the modal!</p>
      <button @click="open = false">Close</button>
    </div>
  </Teleport>
</template>
This renders the modal directly under <body> instead of nested within the component’s DOM structure.

Props

to

  • Type: string | Element | null | undefined
  • Required: Yes
Specifies the target container where the teleported content should be rendered.

String Selector

Must be a valid CSS selector:
<Teleport to="#modal-container">
  <!-- content -->
</Teleport>

<Teleport to=".modal-wrapper">
  <!-- content -->
</Teleport>

<Teleport to="body">
  <!-- content -->
</Teleport>

Element Reference

Can be an actual DOM element:
<script setup>
import { ref, onMounted } from 'vue'

const targetElement = ref(null)

onMounted(() => {
  targetElement.value = document.querySelector('#app-modals')
})
</script>

<template>
  <Teleport :to="targetElement">
    <!-- content -->
  </Teleport>
</template>
The target element must exist in the DOM before the component is mounted. The target cannot be rendered by Vue itself and should ideally be outside of the entire Vue application tree.

disabled

  • Type: boolean
  • Default: false
When true, the content will remain in its original location instead of being teleported.
<Teleport to="body" :disabled="isMobile">
  <!-- On mobile, render in place. On desktop, teleport to body -->
  <div class="modal">Modal content</div>
</Teleport>
This is useful for conditionally teleporting based on media queries or other runtime conditions.

defer

  • Type: boolean
  • Default: false
When true, defers the teleport until after the component has mounted. This is useful when the target element is rendered by Vue in a later phase.
<Teleport to="#late-div" defer>
  <!-- Will wait for #late-div to be rendered -->
  <div>Deferred content</div>
</Teleport>

Common Use Cases

Modals and Dialogs

<template>
  <div class="app">
    <button @click="showModal = true">Show Modal</button>
    
    <Teleport to="body">
      <div v-if="showModal" class="modal-overlay">
        <div class="modal">
          <h2>Modal Title</h2>
          <p>Modal content...</p>
          <button @click="showModal = false">Close</button>
        </div>
      </div>
    </Teleport>
  </div>
</template>

<style>
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9999;
}
</style>

Tooltips and Popovers

<Teleport to="body">
  <div class="tooltip" :style="tooltipPosition">
    {{ tooltipText }}
  </div>
</Teleport>

Notifications

<Teleport to="#notifications">
  <TransitionGroup name="notification">
    <div v-for="notif in notifications" :key="notif.id" class="notification">
      {{ notif.message }}
    </div>
  </TransitionGroup>
</Teleport>

Multiple Teleports to Same Target

Multiple <Teleport> components can target the same container. The order will be a simple append - later mounts will be located after earlier ones within the target element.
<!-- Component A -->
<Teleport to="#modals">
  <div>A</div>
</Teleport>

<!-- Component B -->
<Teleport to="#modals">
  <div>B</div>
</Teleport>

<!-- Result in #modals -->
<!-- <div>A</div> -->
<!-- <div>B</div> -->

Using with Components

<Teleport> works seamlessly with Vue components:
<Teleport to="body">
  <ModalComponent v-if="showModal" @close="showModal = false" />
</Teleport>
The component maintains its logical position in the component tree (props, events, provide/inject all work as expected), but its DOM is rendered in the teleport target.

Using with Transitions

<Teleport to="body">
  <Transition name="modal">
    <div v-if="show" class="modal">
      Modal content
    </div>
  </Transition>
</Teleport>

Custom Elements Support

When used inside a Custom Element, Vue tracks teleport targets to properly manage lifecycle and cleanup.

Namespace Handling

<Teleport> automatically detects the namespace of the target element:
  • If teleporting to an SVG element, content is rendered with SVG namespace
  • If teleporting to a MathML element, content is rendered with MathML namespace
Implementation at /packages/runtime-core/src/components/Teleport.ts:140-143

SSR Behavior

During server-side rendering:
  1. Teleport content is rendered in place as placeholder comments
  2. The actual content is rendered in the target location in the SSR output
  3. During hydration, Vue matches the teleported content with its target location

Implementation Details

  • Anchors: Teleport uses comment nodes (or text nodes in production) as anchors in both the original location and the target location
  • Moving: When disabled changes or target changes, the content is moved between locations
  • Hydration: Special handling during SSR hydration to correctly match teleported content

Accessibility Considerations

When using <Teleport> for modals and dialogs, remember to manage focus and ARIA attributes appropriately. The teleported content should have proper ARIA roles and focus management even though it’s rendered outside the component’s DOM hierarchy.
<Teleport to="body">
  <div
    v-if="showModal"
    role="dialog"
    aria-modal="true"
    aria-labelledby="modal-title"
  >
    <h2 id="modal-title">Modal Title</h2>
    <!-- modal content -->
  </div>
</Teleport>

Source Reference

  • Package: @vue/runtime-core
  • Implementation: /packages/runtime-core/src/components/Teleport.ts:75-343
  • Props Interface: /packages/runtime-core/src/components/Teleport.ts:20-24

See Also

Build docs developers (and LLMs) love