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

Positions
Behaviors

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.

Controlled dialog
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.

Detached trigger
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.

API reference

Primitives

ComponentDescription
DialogGroups all parts of the dialog. Doesn’t render its own HTML element.
DialogTriggerA button that opens the dialog.
DialogPortalA portal element that moves the popup to a different part of the DOM. Renders a <div> element.
DialogBackdropAn overlay displayed beneath the popup. Renders a <div> element.
DialogViewportA positioning container for the dialog popup that can be made scrollable. Renders a <div> element.
DialogPopupA container for the dialog contents. Must be used inside DialogPortal. Renders a <div> element.
DialogHeaderStyled container for convenience purpose. Renders a <div> element.
DialogTitleA heading that labels the dialog. Renders an <h2> element.
DialogDescriptionA paragraph with additional information about the dialog. Renders a <p> element.
DialogFooterStyled container for convenience purpose. Renders a <div> element.
DialogCloseA button that closes the dialog. Renders a <button> element.

Composite Components

ComponentDescription
DialogContentStandard usage. Groups Portal, Backdrop, Viewport, and Popup.
DialogElementOutsideContentExtension of DialogContent for UIs with elements outside the popup.

DialogContent Layout Variants

VariantDescription
responsive (default)Mobile bottom sheet; Desktop centered modal.
centerCentered modal with fade-up transition.
topTop-aligned modal with fade-down transition.
scrollableOptimized for containers with internal scrolling.
stackedScaling and offset support for nested dialogs.
element-outsideSupports interactive elements outside the popup box.