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-ifom 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>