Loading...
Loading...
Teleport is a built-in Vue 3 component that allows rendering content anywhere in the DOM, even outside the Vue app root element.
This is especially useful for modals, tooltips, notifications, and other elements that should be outside the current hierarchy.
<template>
<div class="component">
<button @click="showModal = true">Open Modal</button>
<Teleport to="body">
<div v-if="showModal" class="modal">
<p>I'm rendered in body!</p>
<button @click="showModal = false">Close</button>
</div>
</Teleport>
</div>
</template>
<script setup>
import { ref } from 'vue'
const showModal = ref(false)
</script>
<style scoped>
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
z-index: 1000;
}
</style>
The <Teleport> content will be rendered in <body>, not inside .component.
The to attribute accepts CSS selector or DOM element:
<!-- By ID -->
<Teleport to="#modal-container">
<div>Content</div>
</Teleport>
<!-- By class -->
<Teleport to=".modal-wrapper">
<div>Content</div>
</Teleport>
<!-- By tag -->
<Teleport to="body">
<div>Content</div>
</Teleport>
<script setup>
import { ref, onMounted } from 'vue'
const targetElement = ref(null)
onMounted(() => {
targetElement.value = document.getElementById('custom-container')
})
</script>
<template>
<Teleport :to="targetElement" v-if="targetElement">
<div>Content</div>
</Teleport>
</template>
You can dynamically enable/disable teleportation:
<template>
<Teleport to="body" :disabled="!isMobile">
<div class="modal">
On mobile - in body, on desktop - here
</div>
</Teleport>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const isMobile = ref(false)
onMounted(() => {
isMobile.value = window.innerWidth < 768
})
</script>
You can teleport multiple components to one place:
<!-- Component1.vue -->
<Teleport to="#notifications">
<div>Notification 1</div>
</Teleport>
<!-- Component2.vue -->
<Teleport to="#notifications">
<div>Notification 2</div>
</Teleport>
<!-- index.html -->
<div id="app"></div>
<div id="notifications"></div>
Both notifications will render in #notifications in mount order.
<!-- Modal.vue -->
<template>
<Teleport to="body">
<Transition name="modal">
<div v-if="isOpen" class="modal-overlay" @click="close">
<div class="modal-content" @click.stop>
<button class="modal-close" @click="close">×</button>
<slot />
</div>
</div>
</Transition>
</Teleport>
</template>
<script setup>
defineProps({
isOpen: Boolean
})
const emit = defineEmits(['close'])
function close() {
emit('close')
}
</script>
<style scoped>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 8px;
max-width: 500px;
position: relative;
}
.modal-close {
position: absolute;
top: 10px;
right: 10px;
background: none;
border: none;
font-size: 24px;
cursor: pointer;
}
</style>
Usage:
<template>
<button @click="isModalOpen = true">Open</button>
<Modal :is-open="isModalOpen" @close="isModalOpen = false">
<h2>Title</h2>
<p>Modal content</p>
</Modal>
</template>
<script setup>
import { ref } from 'vue'
import Modal from './Modal.vue'
const isModalOpen = ref(false)
</script>
<template>
<div class="parent" style="position: relative; overflow: hidden">
<div class="modal" style="position: fixed">
<!-- May be cut off due to overflow: hidden -->
</div>
</div>
</template>
Problems:
overflowz-index context<template>
<div class="parent" style="position: relative; overflow: hidden">
<Teleport to="body">
<div class="modal" style="position: fixed">
<!-- Always displays correctly -->
</div>
</Teleport>
</div>
</template>
Advantages:
<!-- Wrong -->
<Teleport to="#non-existent">
<div>Error!</div>
</Teleport>
<!-- Correct -->
<Teleport to="body">
<div>Works!</div>
</Teleport>
<script setup>
import { provide } from 'vue'
provide('theme', 'dark')
</script>
<template>
<Teleport to="body">
<!-- inject still works! -->
<ChildComponent />
</Teleport>
</template>
Props, events, provide/inject continue to work, as only DOM changes, not component hierarchy.
Teleport:
In interviews:
Important to be able to: