Skip to main content
A dialog is a modal window that requires user interaction before returning to the main content. It’s useful for capturing user input, confirming actions, or displaying important information that requires immediate attention.

Installation

npx shadcn@latest add @eo-n/dialog

Usage

Import all parts and piece them together:
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
<Dialog>
  <DialogTrigger>Open</DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Are you sure you want to proceed?</DialogTitle>
      <DialogDescription>
        This action may have permanent effects. Please confirm if you want to
        continue.
      </DialogDescription>
    </DialogHeader>
  </DialogContent>
</Dialog>

Examples

Basic Dialog

import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";

export default function DialogDemo() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Open Dialog</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Are you sure you want to proceed?</DialogTitle>
          <DialogDescription>
            This action may have permanent effects. Please confirm if you want
            to continue.
          </DialogDescription>
        </DialogHeader>
      </DialogContent>
    </Dialog>
  );
}

With Custom Close Button

You can add a custom close button in the footer along with action buttons:
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";

export default function DialogWithFooter() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Open</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Confirm Action</DialogTitle>
          <DialogDescription>
            Are you sure you want to proceed with this action?
          </DialogDescription>
        </DialogHeader>
        <DialogFooter>
          <DialogClose asChild>
            <Button variant="outline">Cancel</Button>
          </DialogClose>
          <Button>Confirm</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

Without Close Icon

Use the hideCloseIcon prop to remove the X button from the dialog:
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";

export default function DialogNoCloseIcon() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Open</Button>
      </DialogTrigger>
      <DialogContent hideCloseIcon>
        <DialogHeader>
          <DialogTitle>No Close Icon</DialogTitle>
          <DialogDescription>
            This dialog doesn't have a close icon. Use the escape key or click
            outside to close.
          </DialogDescription>
        </DialogHeader>
      </DialogContent>
    </Dialog>
  );
}

Nested Dialogs

Dialogs can be nested to create multi-step flows:
import * as React from "react";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";

export default function NestedDialogs() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Open First Dialog</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>First Dialog</DialogTitle>
          <DialogDescription>
            This is the first dialog. You can open another dialog from here.
          </DialogDescription>
        </DialogHeader>
        <Dialog>
          <DialogTrigger asChild>
            <Button>Open Second Dialog</Button>
          </DialogTrigger>
          <DialogContent>
            <DialogHeader>
              <DialogTitle>Second Dialog</DialogTitle>
              <DialogDescription>
                This is a nested dialog on top of the first one.
              </DialogDescription>
            </DialogHeader>
          </DialogContent>
        </Dialog>
      </DialogContent>
    </Dialog>
  );
}

Open On Menu

Control the dialog state to open it from a dropdown menu:
import * as React from "react";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";

export default function DialogFromMenu() {
  const menuTriggerRef = React.useRef<HTMLButtonElement>(null);
  const [dialogOpen, setDialogOpen] = React.useState(false);

  return (
    <>
      <DropdownMenu>
        <DropdownMenuTrigger ref={menuTriggerRef} asChild>
          <Button>Open Menu</Button>
        </DropdownMenuTrigger>
        <DropdownMenuContent>
          <DropdownMenuItem onClick={() => setDialogOpen(true)}>
            Open Dialog
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
      
      <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
        <DialogContent finalFocus={menuTriggerRef}>
          <DialogHeader>
            <DialogTitle>Dialog from Menu</DialogTitle>
            <DialogDescription>
              This dialog was opened from a menu item. Focus will return to
              the menu trigger when closed.
            </DialogDescription>
          </DialogHeader>
        </DialogContent>
      </Dialog>
    </>
  );
}
Make sure to use the dialog’s finalFocus prop to return focus back to the menu trigger for proper accessibility.

API Reference

Dialog

The root component that manages the dialog state.
open
boolean
Controls the open state of the dialog.
defaultOpen
boolean
The initial open state for uncontrolled usage.
onOpenChange
(open: boolean) => void
Callback fired when the open state changes.

DialogTrigger

The button that opens the dialog.
asChild
boolean
default:"false"
Merge props onto the child element instead of wrapping it.

DialogContent

Contains the content to be rendered in the open dialog.
hideCloseIcon
boolean
default:"false"
Hides the X close icon button.
flush
boolean
default:"false"
Removes padding and gap from the content container. Useful when using DialogHeader and DialogFooter with borders.
finalFocus
React.RefObject<HTMLElement>
Element to receive focus when the dialog closes.
className
string
Additional CSS classes to apply.

DialogHeader

Wrapper for the dialog title and description.
className
string
Additional CSS classes to apply.

DialogFooter

Wrapper for dialog action buttons.
className
string
Additional CSS classes to apply.

DialogTitle

The accessible title of the dialog.
className
string
Additional CSS classes to apply.

DialogDescription

The accessible description of the dialog.
className
string
Additional CSS classes to apply.

DialogClose

A button that closes the dialog.
asChild
boolean
default:"false"
Merge props onto the child element instead of wrapping it.

Build docs developers (and LLMs) love