To truly unmount a dialog and animate it out, combine forceUnmount (remove the node when
closed) with delayUnmount (keep it around long enough for the transition to finish). The
component reads isOpen to drive the enter/exit states.
import { createDialog, type DialogComponentProps } from "./dialog-fn";
function Modal({ isOpen, onClose, onConfirm }: DialogComponentProps<void, boolean>) {
return (
<div
style={{
position: "fixed",
inset: 0,
display: "grid",
placeItems: "center",
background: "#0006",
opacity: isOpen ? 1 : 0,
pointerEvents: isOpen ? "auto" : "none",
transition: "opacity .2s ease",
}}
>
<div
className="panel"
style={{
background: "#fff",
borderRadius: 14,
minWidth: 260,
opacity: isOpen ? 1 : 0,
transform: isOpen ? "scale(1)" : "scale(.92)",
transition: "transform .2s ease, opacity .2s ease",
}}
>
<h3>Animated modal</h3>
<p className="muted">
The exit transition plays before the node unmounts, thanks to <code>delayUnmount</code>.
</p>
<div className="row">
<button onClick={onClose}>Close</button>
<button className="primary" onClick={() => onConfirm?.(true)}>
OK
</button>
</div>
</div>
</div>
);
}
const { Dialog, show } = createDialog(Modal, { forceUnmount: true, delayUnmount: 200 });
export default function App() {
return (
<main>
<button className="primary" onClick={() => show()}>
Open animated modal
</button>
<Dialog />
</main>
);
}