Dialogs & Overlays

Modal dialogs, drawers, popovers, and overlay components.

Use Dialog for focused modal interactions, AlertDialog for destructive action confirmations, Drawer or Sheet for slide-out panels, Popover for anchored floating content, and DropdownMenu/ContextMenu for action menus. All overlay components handle focus trapping, keyboard navigation, and backdrop clicks.

This guide is part of the UI Components documentation.

Overlay components are UI elements that appear above the main content layer, capturing user attention for focused interactions while maintaining context through backdrop dimming and focus management.

  • Use Dialog when: displaying forms, settings, or content that needs focus. - Use AlertDialog when: confirming destructive actions (delete, cancel subscription).
  • Use Drawer/Sheet when: showing secondary content that doesn't block the main view.
  • Use Popover when: providing contextual info anchored to an element.
  • If unsure: Dialog for forms, AlertDialog for deletions.

Dialog

Modal dialog for focused interactions.

import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose } from '@kit/ui/dialog';
<Dialog>
<DialogTrigger render={<Button />}>Open Dialog</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogDescription>
This is a description of the dialog content.
</DialogDescription>
</DialogHeader>
<div className="py-4">
Dialog body content
</div>
<DialogFooter>
<DialogClose render={<Button variant="outline" />}>Cancel</DialogClose>
<Button>Confirm</Button>
</DialogFooter>
</DialogContent>
</Dialog>

Controlled Dialog

const [open, setOpen] = useState(false);
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
{/* content */}
</DialogContent>
</Dialog>

Alert Dialog

Confirmation dialog for destructive actions.

import { AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from '@kit/ui/alert-dialog';
<AlertDialog>
<AlertDialogTrigger render={<Button variant="destructive" />}>
Delete
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Delete</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>

Drawer

Slide-out panel from screen edge.

import { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter, DrawerClose } from '@kit/ui/drawer';
<Drawer>
<DrawerTrigger>
<Button>Open Drawer</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Drawer Title</DrawerTitle>
<DrawerDescription>Drawer description</DrawerDescription>
</DrawerHeader>
<div className="p-4">
Drawer content
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose>
<Button variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>

Sheet

Side panel overlay (similar to drawer).

import { Sheet, SheetTrigger, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '@kit/ui/sheet';
<Sheet>
<SheetTrigger render={<Button />}>Open Sheet</SheetTrigger>
<SheetContent side="right">
<SheetHeader>
<SheetTitle>Sheet Title</SheetTitle>
<SheetDescription>Sheet description</SheetDescription>
</SheetHeader>
<div className="py-4">
Sheet content
</div>
</SheetContent>
</Sheet>

Supports side prop: "top", "right", "bottom", "left".

Popover

Floating content anchored to trigger.

import { Popover, PopoverTrigger, PopoverContent } from '@kit/ui/popover';
<Popover>
<PopoverTrigger render={<Button variant="outline" />}>
Open Popover
</PopoverTrigger>
<PopoverContent>
<div className="space-y-2">
<h4 className="font-medium">Popover Title</h4>
<p className="text-sm text-muted-foreground">
Popover content goes here.
</p>
</div>
</PopoverContent>
</Popover>

Menu triggered by button click.

import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuLabel } from '@kit/ui/dropdown-menu';
<DropdownMenu>
<DropdownMenuTrigger
render={
<Button variant="outline">
Options
<ChevronDown className="ml-2 h-4 w-4" />
</Button>
}
/>
<DropdownMenuContent>
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

Context Menu

Right-click menu.

import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem } from '@kit/ui/context-menu';
<ContextMenu>
<ContextMenuTrigger className="flex h-32 w-full items-center justify-center rounded border border-dashed">
Right click here
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem>Copy</ContextMenuItem>
<ContextMenuItem>Paste</ContextMenuItem>
<ContextMenuItem>Delete</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>

In MakerKit, we use Dialog for all settings forms and AlertDialog exclusively for delete confirmations. This consistency helps users build muscle memory - they know AlertDialog always means "this is destructive."

Common Pitfalls

  • Dialog not closing after action: Controlled dialogs need onOpenChange to handle close state. Call setOpen(false) in your action handler or use DialogClose wrapper.
  • Wrong trigger composition pattern: Base UI components in this repo use the render prop pattern for wrapped triggers and close buttons, not Radix asChild.
  • Form submission closing dialog: Form onSubmit prevents dialog close by default. Use controlled state and close after successful submission.
  • Popover z-index issues: Popovers inside other overlays may render behind. Use style={{ zIndex: 100 }} on PopoverContent if needed.
  • AlertDialog vs Dialog confusion: Use AlertDialog only for destructive confirmations. For forms or complex content, use Dialog.
  • Dropdown menu item not clickable: DropdownMenuItem needs an onClick handler, or use the component's render/composition pattern when rendering custom interactive elements.

Frequently Asked Questions

When should I use Dialog vs AlertDialog?
Use Dialog for forms, settings, and general modals. Use AlertDialog specifically for destructive action confirmations (delete, cancel) that need explicit user acknowledgment.
How do I close a dialog programmatically?
Use controlled mode with useState: const [open, setOpen] = useState(false). Call setOpen(false) after your action completes.
What's the difference between Drawer and Sheet?
They're similar slide-out panels. Drawer is typically mobile-friendly with swipe gestures. Sheet supports positioning on any side via the side prop.
Why isn't my Popover appearing?
Ensure PopoverTrigger wraps an interactive element and uses the repo's render pattern when composing with Button. Check that the trigger element is visible and not disabled.
How do I prevent dialog close on backdrop click?
Dialog doesn't have this built-in. Use onOpenChange to conditionally prevent close, or use AlertDialog which requires explicit button clicks.

Next: Data Display →