Het probleem

Stel je bouwt een modal binnen een diep genest component. Het component staat ergens in een container met overflow: hidden of position: relative. Resultaat: de modal valt af of staat op de verkeerde plek.

Met <Teleport> kun je een stuk template op een andere plek in de DOM laten renderen — meestal direct in <body> — terwijl de logica gewoon bij het component blijft.

Gebruik voor:

  • Modals / dialogen
  • Tooltips
  • Notificaties / toast-meldingen
  • Dropdowns die buiten hun parent moeten kunnen overlappen

Niet nodig voor: "gewone" modals die op je hele pagina passen zonder overflow-issues. Voor de meeste student-projecten kun je zonder.

De syntax — één tag

Wikkel het stuk template dat je wilt verplaatsen in <Teleport to="...">:

<template>
  <button @click="show = true">Open modal</button>

  <Teleport to="body">
    <div v-if="show" class="modal-overlay">
      <p>Ik woon visueel in body, maar logisch hier</p>
      <button @click="show = false">Sluiten</button>
    </div>
  </Teleport>
</template>

Wat doet to="body"? Het is een CSS-selector die zegt: "render dit stuk DOM daar". Meestal is body wat je wilt — buiten alle parent-elementen.

Belangrijk: state & events blijven werken

De v-if="show", @click, props — alles werkt nog. Vue rendert het visueel ergens anders, maar gedraagt zich alsof het hier woont. Geen events doorsturen, geen state lifting.

Compleet voorbeeld — Modal

Modal.vue

<!-- src/components/Modal.vue -->
<script setup>
defineProps({
  isOpen: Boolean
})
defineEmits(['close'])
</script>

<template>
  <Teleport to="body">
    <div v-if="isOpen" class="modal-overlay" @click="$emit('close')">
      <div class="modal" @click.stop>
        <slot />
        <button @click="$emit('close')">Sluiten</button>
      </div>
    </div>
  </Teleport>
</template>

<style scoped>
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
}
.modal {
  background: white;
  padding: 24px;
  border-radius: 8px;
  max-width: 400px;
}
</style>

Gebruik (kan diep genest staan)

<script setup>
import { ref } from 'vue'
import Modal from './components/Modal.vue'

const showModal = ref(false)
</script>

<template>
  <div style="position: relative; overflow: hidden;">
    <button @click="showModal = true">Open</button>

    <Modal :isOpen="showModal" @close="showModal = false">
      <p>Hallo!</p>
    </Modal>
  </div>
</template>

Resultaat: de modal verschijnt netjes over de hele pagina, ook al staat hij logisch in een container met overflow: hidden. Zonder Teleport zou de modal binnen die container blijven en raar uitzien.

Belangrijke Regels

  • to="body" is de standaard keuze
  • State en events blijven gewoon werken — Vue doet alleen iets met het renderen
  • Niet nodig voor elke modal — alleen als parents je in de weg zitten
  • Combineer met v-if om de modal te tonen/verbergen

Veelgemaakte Fouten

Fout — naar een element dat niet bestaat:

<Teleport to="#modal-root">...</Teleport>
<!-- ❌ als #modal-root niet in de DOM staat, doet Teleport niks -->

Goed: gebruik "body" (bestaat altijd), of zorg dat het doel-element in je index.html staat.

Fout — vergeten dat clicks doorpropageren:

Klik op de overlay sluit de modal. Klik binnen de modal sluit hem dan ook — tenzij je @click.stop gebruikt:

<div @click="$emit('close')">             <!-- overlay -->
  <div @click.stop>                       <!-- ✅ stopt propagatie -->
    <!-- modal content -->
  </div>
</div>