Teleport in Vue.js
What is Teleport?
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.
Basic Usage
<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.
Selectors for to
The to attribute accepts CSS selector or DOM element:
CSS Selector
<!-- 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>
DOM Element
<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>
Disabling Teleport
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>
Multiple Teleports to One Target
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.
Practical Examples
Modal Window
<!-- 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>
Teleport vs CSS position: fixed
Without Teleport
<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:
- May be cut off by parent
overflow - Inherits
z-indexcontext - Difficult to manage stacking order
With Teleport
<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:
- Independent of parent styles
- Own stacking context
- Easy to manage z-index
Limitations
Target element must exist
<!-- Wrong -->
<Teleport to="#non-existent">
<div>Error!</div>
</Teleport>
<!-- Correct -->
<Teleport to="body">
<div>Works!</div>
</Teleport>
Teleport doesn't change logical hierarchy
<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.
Conclusion
Teleport:
- Renders content anywhere in DOM
- Doesn't change logical component hierarchy
- Useful for modals, tooltips, notifications
- Solves overflow and z-index problems
- Can be disabled dynamically
- Supports multiple teleports to one target
In interviews:
Important to be able to:
- Explain what Teleport is and why it's needed
- Show usage examples (modals, notifications)
- Describe problems Teleport solves
- Explain that logical hierarchy doesn't change
- Give differences from CSS position: fixed