React
Teleport in Vue 3: modali fuori dal DOM del genitore
Teleport sposta la modale sotto body, dove nessun overflow o transform del genitore la può tagliare. Il componente resta lo stesso, cambia il nodo.
In sintesi
- Teleport sposta il DOM, non l’istanza: provide, inject ed emit continuano a funzionare.
- Il v-if va dentro il Teleport, altrimenti il nodo viene creato e distrutto a ogni apertura.
- Teleport non gestisce focus trap, scroll lock o chiusura con ESC: quelli restano da scrivere.
Una modale in Vue 3 esce dalla gerarchia visiva del genitore con <Teleport>: il nodo finisce sotto body, dove nessun overflow: hidden o transform lo può tagliare.
Il problema che risolve
Un pannello con position: fixed si posiziona rispetto all’antenato con transform, filter o perspective più vicino, non rispetto alla finestra. Dentro una card animata la modale appare ritagliata e l’overlay copre metà schermo.
import { ref } from "vue";
const open = ref(false);
Open
Title
Close
L’attributo to accetta un selettore CSS o un elemento. Il v-if sta dentro il Teleport: così il contenuto non esiste nel DOM quando la modale è chiusa.
Un contenitore dedicato, oppure no
...
-->
Un contenitore vuoto in index.html evita di appoggiarsi a body: se un altro script monta un elemento sul body, il tuo nodo resta tuo. Con :disabled il Teleport si spegne e il contenuto torna al suo posto, utile nei test.
Teleport, dialog nativo, libreria
| Caso | Vince | Perché |
|---|---|---|
| Modale con form e stato interno | Teleport più componente | Il contenuto resta un componente Vue |
| Avviso breve senza focus | Teleport su body | Basta un nodo, niente gestione del focus |
| Focus trap, scroll lock, ESC | Libreria headless | Accessibilità già scritta e testata |
| Progetto senza dipendenze | Teleport | Zero installazioni, il resto lo scrivi tu |
Errori comuni
- Mettere
v-ifsul Teleport invece che sul contenuto: il nodo target nasce e muore a ogni apertura. - Puntare a un id che non esiste: Vue avvisa in console e non renderizza nulla.
- Dimenticare il focus: la modale si apre con il cursore rimasto sul pulsante dietro.
- Lasciare il Teleport attivo in un test: il contenuto finisce fuori dal wrapper montato e le query falliscono.
Il limite da mettere in conto
Teleport sposta il DOM, non il contesto: provide, inject, emit e listener continuano a funzionare perché l’istanza non cambia. Non risolve però focus trap e blocco dello scroll, che restano da scrivere o da prendere da una libreria headless.
Dove continuare
Nel catalogo: shadcn-vue, PrimeVue e vue-bits; i blocchi di dialogo sono in /blocks. Le guide: Composition API e Pinia. I confronti: portal in React e Vue vs React.
Domande frequenti
Posso usare Teleport per una modale accessibile?
Sì, ma Teleport gestisce solo la posizione nel DOM. Focus trap, blocco dello scroll e chiusura con ESC vanno scritti o presi da una libreria headless.
Si perde il contesto del componente?
No. L’istanza resta la stessa: provide, inject, emit e stato reattivo funzionano come prima dello spostamento.
Perché la modale non compare?
Il selettore in to non trova nulla. Vue avvisa in console e salta il rendering: controlla che il nodo esista prima del mount.
Teleport funziona con il rendering lato server?
Sì, con un’avvertenza: sul server il contenuto viene reso dove è dichiarato. Usa l’attributo disabled durante l’idratazione per evitare disallineamenti.
Serve un contenitore dedicato?
Non è obbligatorio. body funziona, ma un nodo tuo evita conflitti con altri script che montano sullo stesso body.