Objetivo y requisitos
Teleport permite renderizar una parte del DOM fuera de su contenedor original sin cambiar la relación lógica entre componentes. Props, eventos y reactividad siguen funcionando. Crearemos notificaciones que se muestran directamente en el body, aunque el botón viva en otro lugar. El ejemplo usa Vue 3 y TypeScript en una aplicación de navegador.
Un componente completo
Guárdalo en src/App.vue. Cada notificación tiene un identificador estable y su propio temporizador. Al desmontar el componente limpiamos todos los temporizadores pendientes.
<script setup lang="ts">
import { onUnmounted, ref } from 'vue'
type Note = { id: number; text: string }
const notes = ref<Note[]>([])
const timers = new Map<number, ReturnType<typeof setTimeout>>()
let nextId = 0
function removeNotification(id: number) {
const timer = timers.get(id)
if (timer !== undefined) clearTimeout(timer)
timers.delete(id)
notes.value = notes.value.filter((note) => note.id !== id)
}
function addNotification(text: string) {
const id = ++nextId
notes.value.push({ id, text })
timers.set(id, setTimeout(() => removeNotification(id), 4000))
}
onUnmounted(() => {
timers.forEach((timer) => clearTimeout(timer))
timers.clear()
})
</script>
<template>
<main>
<h1>Notificaciones con Teleport</h1>
<button @click="addNotification('Los cambios se guardaron')">Mostrar notificación</button>
</main>
<Teleport to="body">
<section class="notifications" aria-label="Notificaciones" aria-live="polite" aria-relevant="additions">
<div v-for="note in notes" :key="note.id" class="notification">
<p>{{ note.text }}</p>
<button :aria-label="'Cerrar: ' + note.text" @click="removeNotification(note.id)">Cerrar</button>
</div>
</section>
</Teleport>
</template>
<style scoped>
.notifications { position: fixed; right: 1rem; bottom: 1rem; z-index: 1000; display: grid; gap: .75rem; width: min(22rem, calc(100vw - 2rem)); }
.notification { padding: 1rem; border-radius: .75rem; background: #134e4a; color: white; box-shadow: 0 4px 16px rgb(0 0 0 / 20%); }
</style>
Resultado esperado
Ejecuta npm run dev y pulsa varias veces el botón. Las notificaciones aparecen en la esquina inferior, muestran su texto y desaparecen individualmente después de cuatro segundos. Cierra una manualmente: las demás deben conservar su temporizador. En el inspector verás el contenedor de notificaciones bajo body. Prueba un ancho de pantalla pequeño y ejecuta npm run build.
Destinos, disabled y defer
El destino debe existir cuando se monta el Teleport. body ya existe en esta aplicación. Para un destino propio, añade primero un contenedor como <div id="notifications"></div> y usa to="#notifications".
La prop disabled mantiene el contenido en su posición original; puedes enlazarla a un estado reactivo. En Vue 3.5+, defer retrasa la resolución del destino hasta que se montan otras partes del mismo ciclo de actualización. No sirve para esperar un contenedor que aparezca arbitrariamente varios segundos después.
Errores frecuentes y otros usos
Renderiza note.text, no el objeto completo, y usa :key="note.id" para conservar la identidad de cada aviso. No retires siempre el primer elemento de la cola: el botón y el temporizador deben retirar el identificador que les corresponde. El estado tipado evita que un array vacío se infiera con un tipo incorrecto.
Teleport también puede ayudar a sacar modales o tooltips de contenedores con overflow o transformaciones. Debes gestionar además posición, foco y teclado según el componente: mover el DOM por sí solo no resuelve esos comportamientos. Para un modal con foco nativo, consulta defineExpose con un diálogo reutilizable.
Consulta la guía oficial de Teleport. Para renderizado en servidor, sigue sus recomendaciones específicas de hidratación y destinos.
Sigue leyendo
Una idea lleva a otra.
)
)
)
)