Dialog

A popup that opens on top of the entire page.

Installation

pnpm dlx shadcn@latest add @lumi-ui/dialog

Basic Usage

import {
  Dialog,
  DialogClose,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogContent,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
 
export function DialogDemo() {
  return (
    <Dialog>
      <DialogTrigger>Show Alert 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>
    <DialogScrollArea />     
    <DialogFooter>     
      <DialogClose />     
    </DialogFooter>   
  </DialogContent> 
</Dialog>

Cookbook

Controlled dialog

By default, Dialog is an uncontrolled component that manages its own state. Use the open and onOpenChange props to control the dialog state. This is useful when triggering the dialog from external events or performing async actions before closing.

Controlled dialog
const [open, setOpen] = React.useState(false);
 
<Dialog open={open} onOpenChange={setOpen}>
  <DialogTrigger>Open</DialogTrigger>
  <DialogContent>
    <form 
      // Close the dialog once the form data is submitted           
      onSubmit={
        async () => {             
          await submitData();             
          setOpen(false);   
        }
      }
    >
      ...
    </form>
  </DialogContent>
</Dialog>

It’s also common to use onOpenChange if your app needs to do something when the dialog is closed or opened. This is recommended over useEffect when reacting to state changes.

Running code when dialog state changes
<Dialog
  open={open}
  onOpenChange={(open) => {
    // Do stuff when the dialog is closed
    if (!open) {
      doStuff();
    }
    // Set the new state
    setOpen(open);
  }}
>

Open from Dropdown menu

To open a dialog from a DropdownMenu, use the controlled state pattern. The Dialog should be placed outside the DropdownMenu to avoid nesting issues.

Nested dialogs

When opening a dialog on top of another, use the DialogStackedContent component. This component includes specific CSS logic to create a visual "stacking" scale effect, allowing the parent dialog to recede slightly when the child opens.

Close confirmation

This example demonstrates a nested confirmation dialog that triggers if the user attempts to discard unsaved input. To achieve the correct visual stacking effect where the parent form recedes into the background, use DialogStackedContent and AlertDialogStackedContent.

Inside scroll

Use DialogScrollableContent combined with DialogScrollArea. This layout fixes the header and footer in place while allowing the inner content body to scroll independently, ensuring the modal never exceeds the viewport height.

Outside scroll

To allow the entire dialog—including the header and footer—to scroll with the page, use DialogViewport as the scrolling container. This approach is useful for very long content where internal scrolling is not desired.

Placing elements outside the popup

To visually place elements (like a close button) outside the main popup surface while keeping them accessible and within the focus trap, nest them inside DialogPopup. The DialogPopup container has pointer-events: none by default, while the inner content wrapper has pointer-events: auto. You must explicitly apply pointer-events-auto to any detached elements to make them interactive.

Detached Triggers

If you need to trigger a dialog from a completely different part of the component tree, or if nesting the trigger inside the root is impractical, use createDialogHandle to link a DialogTrigger to a Dialog remotely.

Detached trigger
import { createDialogHandle } from "@/components/ui/dialog"
 
const demoDialog = createDialogHandle();
 
<DialogTrigger handle={demoDialog}>Open</DialogTrigger>
 
<Dialog handle={demoDialog}>
  ...
</Dialog>

Multiple triggers & payloads

A single dialog can serve multiple triggers. You can pass a payload from the trigger to the dialog to render dynamic content based on which element opened it. This is highly effective for list views where every item needs an "Edit" or "Delete" modal without rendering a separate dialog instance for each row.

Multiple triggers within the Root part
<Dialog>
  <DialogTrigger>Trigger 1</DialogTrigger>
  <DialogTrigger>Trigger 2</DialogTrigger>
  ...
</Dialog>
multiple detached triggers
const demoDialog = createDialogHandle();
 
<DialogTrigger handle={demoDialog}>Trigger 1</DialogTrigger>
<DialogTrigger handle={demoDialog}>Trigger 2</DialogTrigger>
<Dialog handle={demoDialog}>
  ...
</Dialog>

The dialog can render different content depending on which trigger opened it. This is achieved by passing a payload to the <DialogTrigger> and using the function-as-a-child pattern in <Dialog>.

Controlled mode with multiple triggers

When controlling a multi-trigger dialog externally, you must manage both the open state and the triggerId. The onOpenChange event provides eventDetails containing the ID of the trigger that initiated the action.

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.
DialogTitleA heading that labels the dialog. Renders an <h2> element.
DialogDescriptionA paragraph with additional information about the dialog. Renders a <p> element.
DialogCloseA button that closes the dialog. Renders a <button> element.
DialogHeaderStyled container for convenience purpose. Renders a <div> element.
DialogFooterStyled container for convenience purpose. Renders a <div> element.
DialogScrollAreaScrollable container for the dialog contents. Renders <ScrollArea>.

Composite components

ComponentDescription
DialogContentRecommended. Wraps Portal, Backdrop, Viewport, and Popup with standard centered styling.
DialogStackedContentWraps Portal, Backdrop, Viewport and Popup with animation logic for nested "stacking" effects.
DialogScrollableContentWraps Portal, Viewport, and Popup with layout logic for fixed headers/footers and scrollable content.