Dialog
A popup that opens on top of the entire page.
Installation
pnpm dlx shadcn@latest add @lumi-ui/dialog
Add the following utilities to your globals.css:
@utility animate-fade-up {
@apply transition-all duration-300 ease-[cubic-bezier(0.25,1,0.5,1)];
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
translate: 0 100%;
}
}
@utility animate-fade-down {
@apply transition-all duration-300 ease-[cubic-bezier(0.25,1,0.5,1)];
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
translate: 0 -100%;
}
}
@utility animate-slide-left {
@apply transition-all duration-300 ease-[cubic-bezier(0.25,1,0.5,1)];
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
translate: 100% 0;
}
}
@utility animate-slide-right {
@apply transition-all duration-300 ease-[cubic-bezier(0.25,1,0.5,1)];
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
translate: -100% 0;
}
}
@utility animate-fade-zoom {
@apply transition-all duration-200 ease-[cubic-bezier(0.25,1,0.5,1)];
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
scale: 0.94;
}
}
@utility animate-fade {
@apply transition-all duration-200;
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
}
}If you prefer to write inline css in popupVariants, you can replace the following with utility:
data-[starting-style]:scale-94 data-[starting-style]:opacity-0
data-[ending-style]:scale-94 data-[ending-style]:opacity-0
transition-all duration-200 ease-[cubic-bezier(0.25,1,0.5,1)]Basic Usage
import {
Dialog,
DialogClose,
DialogDescription,
DialogFooter,
DialogHeader,
DialogContent,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
export function DialogDemo() {
return (
<Dialog>
<DialogTrigger>Open Dialog</DialogTrigger>
<DialogContent showCloseButton>
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete your
account and remove your data from our servers.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose>Cancel</DialogClose>
<DialogClose>Continue</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
}Anatomy
<Dialog>
<DialogTrigger />
<DialogContent>
<DialogHeader>
<DialogTitle />
<DialogDescription />
</DialogHeader>
<DialogFooter>
<DialogClose />
</DialogFooter>
</DialogContent>
</Dialog>Variants
Cookbook
Scrolling
Internal Scrolling
Set DialogContent to layout="scrollable". Use ScrollArea or overflow-y-auto for the content. Elements outside the scrollable area remain fixed.
Elements Outside
Use layout="element-outside" for designs where controls should live outside the popup.
Viewport Scrolling
For very long content, use DialogViewport as the scrolling container. This allows the entire dialog to scroll with the page.
Custom Implementation
Close Confirmation
Use layout="stacked" for both DialogContent and AlertDialogContent to handle nested confirmation flows gracefully.
Controlled State
Manage the open state manually for external triggers or async closing logic.
const [open, setOpen] = React.useState(false);
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger>Open</DialogTrigger>
<DialogContent>
<form onSubmit={async () => {
await submitData();
setOpen(false);
}}>
...
</form>
</DialogContent>
</Dialog>From Dropdown Menu
Use the controlled state when opening a dialog from a DropdownMenu to avoid focus and nesting issues.
Detached Triggers & Payloads
Use createDialogHandle to link a DialogTrigger to a Dialog when they cannot be nested within the same root.
const demoDialog = createDialogHandle();
<DialogTrigger handle={demoDialog}>Open</DialogTrigger>
<Dialog handle={demoDialog}>...</Dialog>Dynamic Content
A single dialog can handle multiple triggers and render content dynamically based on a payload.
Controlled Multi-Trigger
When controlling multiple triggers externally, use eventDetails.source in onOpenChange to identify the active trigger.