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.

shell
npm install overlay-kit
npx shadcn@latest add dialog

Dialog 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>
  );
}