With shadcn/ui
Learn how to use shadcn/ui’s Dialog component together with overlay-kit.
Installation
shadcn/ui copies component source code directly into your project via its CLI. Add the Dialog component with the command below.
npm install overlay-kit
npx shadcn@latest add dialogDialog is built on top of Radix UI. The shadcn CLI creates components/ui/dialog.tsx and installs the required dependencies (@radix-ui/react-dialog, lucide-react, etc.) automatically.
Basic Usage
shadcn Dialog takes an open prop for visibility and onOpenChange for dismissal. onOpenChange receives a boolean, so calling close() when !open covers backdrop clicks and the Escape key naturally.
import { OverlayProvider, overlay } from 'overlay-kit'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from './components/ui/dialog'; function App() { return ( <button className="inline-flex items-center justify-center rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-800" onClick={() => { overlay.open(({ isOpen, close }) => ( <Dialog open={isOpen} onOpenChange={(open) => !open && close()}> <DialogContent> <DialogHeader> <DialogTitle>Are you sure you want to continue?</DialogTitle> <DialogDescription>This action cannot be undone.</DialogDescription> </DialogHeader> <DialogFooter className="gap-2"> <button className="rounded-md border border-gray-200 px-4 py-2 text-sm font-medium hover:bg-gray-100" onClick={close} > No </button> <button className="rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-800" onClick={close} > Yes </button> </DialogFooter> </DialogContent> </Dialog> )); }} > Open Confirm Dialog </button> ); } export function Example() { return ( <OverlayProvider> <App /> </OverlayProvider> ); }
Receiving Async Results
Use overlay.openAsync to receive the user’s choice as a Promise. Pass the result through close(value) on each button, and provide a default value when the dialog is dismissed via backdrop or Escape.
import { useState } from 'react'; import { OverlayProvider, overlay } from 'overlay-kit'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from './components/ui/dialog'; function App() { const [result, setResult] = useState<boolean | null>(null); return ( <div> <p className="mb-2 text-sm">Result: {result === null ? 'Not selected' : result ? 'Yes' : 'No'}</p> <button className="inline-flex items-center justify-center rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-800" onClick={async () => { const confirmed = await overlay.openAsync<boolean>(({ isOpen, close }) => ( <Dialog open={isOpen} onOpenChange={(open) => !open && close(false)}> <DialogContent> <DialogHeader> <DialogTitle>Are you sure you want to continue?</DialogTitle> <DialogDescription>This action cannot be undone.</DialogDescription> </DialogHeader> <DialogFooter className="gap-2"> <button className="rounded-md border border-gray-200 px-4 py-2 text-sm font-medium hover:bg-gray-100" onClick={() => close(false)} > No </button> <button className="rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-800" onClick={() => close(true)} > Yes </button> </DialogFooter> </DialogContent> </Dialog> )); setResult(confirmed); }} > Open Confirm Dialog </button> </div> ); } export function Example() { return ( <OverlayProvider> <App /> </OverlayProvider> ); }
Releasing Memory After Animation
shadcn Dialog does not expose a dedicated animation-complete callback. Call close in onOpenChange and schedule unmount with setTimeout for the animation duration. This handles button clicks, backdrop clicks, the Escape key, and the X button uniformly.
import { OverlayProvider, overlay } from 'overlay-kit'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from './components/ui/dialog'; function App() { return ( <button className="inline-flex items-center justify-center rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-800" onClick={() => { overlay.open(({ isOpen, close, unmount }) => ( <Dialog open={isOpen} onOpenChange={(open) => { if (!open) { close(); // shadcn dialog's close animation is around 200ms. setTimeout(unmount, 200); } }} > <DialogContent> <DialogHeader> <DialogTitle>Are you sure you want to continue?</DialogTitle> <DialogDescription>This action cannot be undone.</DialogDescription> </DialogHeader> <DialogFooter className="gap-2"> <button className="rounded-md border border-gray-200 px-4 py-2 text-sm font-medium hover:bg-gray-100" onClick={close} > No </button> <button className="rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white hover:bg-gray-800" onClick={close} > Yes </button> </DialogFooter> </DialogContent> </Dialog> )); }} > Open Confirm Dialog </button> ); } export function Example() { return ( <OverlayProvider> <App /> </OverlayProvider> ); }