Hack Frontend Community

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.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-index context
  • 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