Using Vue.js Teleport to Render Modals Outside the App Root
Learn how Vue Teleport moves a component's rendered DOM to a different target while preserving its reactivity, with a worked example, limits, and verification steps.
04 Jun 2026, 17:25 UTC

Useful answer
Vue Teleport lets you render a part of a component’s template into a different DOM node without changing the component’s logical parent‑child relationship. This is ideal for modals, tooltips, or overlays that need to escape stacking contexts or be placed at document.body while keeping props, events, and provide/inject tied to the original component.
How Teleport works
When Vue encounters a <teleport> element, it renders the inner content normally but, at mount time, moves the resulting DOM nodes to the target specified by the to attribute. The target can be any valid CSS selector, a function returning a node, or an array of targets. Vue does not alter the component’s reactivity; data, computed properties, methods, and lifecycle hooks all behave as if the content stayed in place.
Worked configuration: a modal that teleports to #modal-host
1. Set up a Vue 3 project
Run the following in a terminal with write access to your workspace:
# Create a new Vue 3 project using Vite
npm create vue@latest teleport-demo -- --template vue
cd teleport-demo
npm install
No special permissions are required beyond typical npm execution.
2. Add a target container in the HTML
Open public/index.html and insert a div where the modal will appear:
<body>
<div id="app"></div>
<!-- Teleport target -->
<div id="modal-host"></div>
</body>
3. Create a reusable modal component
Create src/components/Modal.vue:
<template>
<teleport to="#modal-host">
<div class="modal-backdrop" @click.self="$emit('close')">
<div class="modal-content">
<slot></slot>
<button @click="$emit('close')">Close</button>
</div>
</div>
</teleport>
</template>
<script setup>
// The modal receives props and emits events normally
defineProps({
title: { type: String, default: 'Modal' }
});
</script>
<style scoped>
.modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: white;
padding: 1.5rem;
border-radius: 8px;
width: 90%;
max-width: 400px;
}
</style>
4. Use the modal in a parent component
Edit src/App.vue to trigger the modal:
<template>
<button @click="show = true">Open modal</button>
<Modal v-if="show" @close="show = false">
<h2>{{ title }}</h2>
<p>This content is rendered inside #modal-host, but the modal component remains a child of App.</p>
</Modal>
</template>
<script setup>
import { ref } from 'vue'
import Modal from './components/Modal.vue'
const show = ref(false)
const title = ref('Hello from Teleport')
</script>
5. Run and inspect
Start the dev server:
npm run dev
Open the app, click “Open modal”, then use the browser’s developer tools to inspect the DOM. You will see the modal’s markup inside #modal-host, not inside the Vue root (#app). The component’s state (show) and any props/events still belong to App.vue.
Limits and common mistakes
- Server‑side rendering (SSR): If you render the page on the server, the target element (
#modal-host) exists only in the browser. The server‑generated HTML will lack the teleported nodes, causing a hydration mismatch. To avoid this, either disable teleport during SSR or render a placeholder on the server and replace it on the client. - Target removed before unmount: If the element matched by
tois deleted from the DOM while the component is still mounted, Vue will emit a warning and move the teleported content todocument.bodyas a fallback. Ensure the target persists for the lifetime of the teleported content, or guard the teleport withv-ifthat checks the target’s existence. - Multiple teleports to the same target: Vue appends each teleport’s nodes in the order they are mounted. If you need precise ordering, consider using a wrapper element or managing insertion order yourself.
- Provide / inject still works: Even though the DOM moves, the component tree remains unchanged, so provide/inject relationships are unaffected.
- Do not confuse Teleport with
portal-vueor custom solutions: Teleport is a built‑in feature (Vue 3) or an official plugin (Vue 2). Using third‑party libraries may introduce additional complexity.
Verification steps
- Mount a component that contains a
<teleport to='#modal-host'>block. - Open the browser’s Elements panel and confirm that the teleported nodes appear inside the element with id
modal-host. - Remove the target element via the console:
document.getElementById('modal-host').remove(). - Observe a warning in the console similar to
[Vue warn]: Teleport target is not found. Falling back to document.body. - Check that the teleported content now resides inside
<body>.
These steps confirm both the normal behavior and the fallback mechanism without altering application state.
Practical tips
- Use Teleport for UI that must escape CSS
overflow: hiddenorz-indexconstraints of parent containers. - When building a reusable modal library, expose a
teleportToprop so consumers can decide where the modal lands. - In Vue 2, install the official
vue-portalplugin and use<portal target='#modal-host'>with similar semantics. - Always test the fallback scenario (target removal) in development to ensure your UI degrades gracefully.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.