# Portal per modali e tooltip, senza trappole

createPortal sposta il nodo nel body e lascia eventi e contesto nell'albero React: la modale esce dal CSS, non dai componenti.

> Pagina: https://ui.eneawork.it/blog/react-portal-modali
> Markdown: https://ui.eneawork.it/blog/react-portal-modali.md

*2026-11-01*

Un portal rende un figlio in un nodo DOM diverso, di solito document.body, e lascia intatti contesto, stato ed eventi React. Serve a far uscire modali e tooltip da un contenitore con overflow nascosto o con uno z-index che li taglia.

Il catalogo EneUI conta 111.985 componenti, 1.556 blocchi e 12.808 icone da 1.472 librerie, misurati da /api/stats il 1 novembre 2026. La cifra viene da una query all'endpoint, non da una stima: conta i record pubblicati, non la qualità dei componenti.

## Cosa resta e cosa se ne va

Il nodo si sposta nel body; gli eventi bolle seguono l'albero React, non il DOM. Un click dentro la modale raggiunge ancora l'handler del componente genitore, e questo sorprende chi si aspetta l'isolamento.

## Il caso eseguibile

import { createPortal } from "react-dom"; import { useEffect, useRef } from "react"; export function Modale({ aperta, onChiudi, children }) { const ritorno = useRef(null); useEffect(() => { if (!aperta) return; ritorno.current = document.activeElement; const onKey = (e) => e.key === "Escape" && onChiudi(); document.addEventListener("keydown", onKey); return () => { document.removeEventListener("keydown", onKey); ritorno.current?.focus(); }; }, [aperta, onChiudi]); if (!aperta) return null; return createPortal( {children} , document.body, ); }

Il codice sposta il contenuto nel body, chiude con Escape e riporta il focus sull'elemento che ha aperto la modale. La trappola del focus non è inclusa: per quella conviene una primitiva Radix o React Aria, entrambe con licenza MIT.

## Tooltip: portal o no

Un tooltip dentro un contenitore con overflow nascosto viene tagliato, e il portal lo salva. Il prezzo è il posizionamento: le coordinate vanno calcolate rispetto al viewport e aggiornate allo scroll, un lavoro che Floating UI, licenza MIT, fa già.

## Record vivi nel catalogo

La categoria dialog raccoglie le modali del catalogo, con il dialog di Origin UI su primitive Radix; per le etichette brevi c'è la categoria tooltip.

## La limitazione

Il portal non porta con sé il CSS: uno stile scritto sotto un contenitore smette di applicarsi nel body, e le variabili CSS definite su un wrapper non arrivano al nodo spostato. I token vanno sul selettore radice, non su un div intermedio.

## Dove continuare

La checklist dei componenti accessibili è in componenti React accessibili; le primitive sono in Radix, Base UI e React Aria; il vocabolario ARIA è in che cos'è ARIA. Il confronto fra React e Svelte sui siti di contenuto è in Svelte vs React.

## FAQ

### Un portal rompe il contesto React?

No. Il contesto segue l'albero dei componenti, quindi un valore del provider resta leggibile dentro il portal anche se il nodo sta nel body.

### Perché la mia modale non prende gli stili?

Perché il nodo vive nel body e non sotto il contenitore che definiva gli stili. Sposta i token sul selettore radice o aggiungi una classe al nodo del portal.

### Il click fuori dalla modale chiude da solo?

No. Serve un listener sul documento che confronti il bersaglio con il nodo della modale, e va rimosso alla chiusura.

### Meglio portal o dialog nativo?

Il dialog nativo gestisce focus, Escape e livello superiore senza JavaScript. Il portal serve quando ti servono animazioni o un controllo più fine del markup.

### Posso usare un portal per un menu a tendina?

Sì, ma il menu deve restare raggiungibile da tastiera e chiudersi con Escape. Un menu in portal senza gestione del focus è peggio di un menu ritagliato.
