Skip to content
shadcn.io
shadcn.io is not affiliated with official shadcn/ui

Shadcn Dialog for React and Tailwind

A modal window that appears on top of the main content.

Installation

bunx --bun shadcn@latest add https://kit.dev/r/dialog.json

Anatomy

Dialog
├── DialogTrigger
└── DialogContent
    ├── DialogOverlay (built in)
    ├── DialogPositioner (built in)
    ├── DialogHeader
    │   ├── DialogTitle
    │   └── DialogDescription
    ├── DialogBody
    ├── DialogFooter
    └── DialogClose

Usage

import { 
  Dialog, 
  DialogTrigger, 
  DialogContent, 
  DialogHeader,
  DialogTitle, 
  DialogDescription, 
  DialogBody,
  DialogFooter,
  DialogClose,
} from "@/components/ui/dialog";
<Dialog>
  <DialogTrigger />
  <DialogContent>
    <DialogHeader>
      <DialogTitle />
      <DialogDescription />
    </DialogHeader>
    <DialogBody>
      {/** your content here */}
    </DialogBody>
    <DialogFooter>
      <DialogClose />
    </DialogFooter>
  </DialogContent>
</Dialog>

Controlled

Use open and onOpenChange on the root to control the dialog state.

Title & Description

DialogHeader supports two usage patterns:

Using props

Pass title and description props directly to DialogHeader.

<DialogHeader title="Dialog Title" description="Dialog description" />

This approach does not require DialogTitle or DialogDescription components.

Using components

Use DialogTitle and DialogDescription as children for more control.

<DialogHeader>
  <DialogTitle>Dialog Title</DialogTitle>
  <DialogDescription>Dialog description</DialogDescription>
</DialogHeader>

Root Provider

Use useDialog with DialogRootProvider when you need the dialog API outside the tree.

Size

Use the size prop on DialogContent to control the maximum width of the panel.

Examples

Inside scroll

Use DialogBody to scroll the content area while header and footer stay fixed.

Outside scroll

Make the positioner scroll so the dialog can extend past the viewport.

No Close Button

Use the showCloseButton={false} prop to hide the close button in the top right corner.

Close behavior

Use closeOnInteractOutside and closeOnEscape props to prevent closing on outside click and escape.

Open from Menu

Open a dialog imperatively from a menu item using the onSelect handler.

Non-Modal

Use modal={false} to allow interaction with elements outside the dialog.

Initial focus

Use initialFocusEl to control which element receives focus when the dialog opens.

Final focus

Use finalFocusEl to control which element receives focus when the dialog closes. Defaults to the trigger.

Lazy mount

shadcn.io sets lazyMount and unmountOnExit to true by default. Keep Dialog mounted and control it with open / onOpenChange instead of unmounting the root.

Nested

Nest dialogs. The parent content receives data-has-nested and --nested-layer-count for scale and fade.

Context

Use DialogContext or useDialogContext to read open state.

Multiple triggers

Share one dialog across triggers. Pass value on each DialogTrigger and handle onTriggerValueChange.

Confirmation

Intercept close when there are unsaved changes.

With tooltip

Wrap the trigger in a Tooltip.

Alert dialog

For destructive or critical confirmations, use Alert Dialog (role="alertdialog"). It focuses a safe action and does not close on outside click.

Custom spacing

Use [--space:--spacing("value")] on DialogContent to adjust internal padding.

Default spacing is --spacing(6).

You can use breakpoint utilities to change the internal spacing at different screen sizes.

md:[--space:--spacing(6)] lg:[--space:--spacing(8)]

Guides

Close behavior

  • closeOnEscape={false} — do not close on Escape
  • closeOnInteractOutside={false} — do not close on outside click
  • For conditional control, use onEscapeKeyDown or onInteractOutside and call event.preventDefault()

Conditional rendering

Unmounting Dialog when toggling open can break focus, scroll lock, and cleanup. Keep the root mounted and control it with open / onOpenChange. shadcn.io already sets lazyMount and unmountOnExit so portal content leaves the DOM while closed.

Z-index

Stacked and nested dialogs use --layer-index and --nested-layer-count. Overlay and content already offset from z-50.

Dynamic imports

When using lazyMount with React.lazy or Next.js dynamic, wrap the imported tree in Suspense.

API Reference

shadcn.io wraps Ark UI Dialog. Defaults below are shadcn.io values. lazyMount and unmountOnExit default to true (Ark: false). preventScroll follows modal, and is forced off inside docs previews.

asChild merges props onto a single child element.

Dialog

Root. Renders no extra chrome; overlay and positioner are created by DialogContent.

PropTypeDefaultDescription
aria-labelstring-Label when DialogTitle is not rendered.
closeOnEscapebooleantrueClose when Escape is pressed.
closeOnInteractOutsidebooleantrueClose when the outside is clicked.
defaultOpenbooleanfalseUncontrolled initial open state.
defaultTriggerValuestring-Uncontrolled initial active trigger value.
finalFocusEl() => MaybeElement-Element to focus when the dialog closes.
hideMode"display-none" | "activity""display-none"How to hide mounted-but-closed content. activity needs React 19+.
idstring-Unique id for the machine.
idsPartial<{ trigger: string | ((value?: string) => string); positioner: string; backdrop: string; content: string; closeTrigger: string; title: string; description: string }>-Element ids for composition.
immediateboolean-Apply presence changes immediately instead of the next frame.
initialFocusEl() => MaybeElement-Element to focus when the dialog opens.
lazyMountbooleantrueMount content on first open.
modalbooleantrueTrap pointer events and hide content behind the dialog.
onEscapeKeyDown(event: KeyboardEvent) => void-Called when Escape is pressed.
onExitComplete() => void-Called when the close animation finishes.
onFocusOutside(event: FocusOutsideEvent) => void-Called when focus moves outside.
onInteractOutside(event: InteractOutsideEvent) => void-Called on outside interaction.
onOpenChange(details: OpenChangeDetails) => void-Called when open state changes. OpenChangeDetails is { open: boolean }.
onPointerDownOutside(event: PointerDownOutsideEvent) => void-Called on pointer down outside.
onRequestDismiss(event: LayerDismissEvent) => void-Called when a parent layer dismisses this one.
onTriggerValueChange(details: TriggerValueChangeDetails) => void-Called when the active trigger changes. { value: string }.
openboolean-Controlled open state.
persistentElements(() => Element | null)[]-Elements that keep pointer events and do not dismiss the dialog.
presentboolean-Controlled presence.
preventScrollbooleantrue when modalPrevent scrolling behind the dialog. Off in docs previews.
restoreFocusboolean-Restore focus to the previously focused element.
role"dialog" | "alertdialog""dialog"Dialog role. Use Alert Dialog for critical confirms.
skipAnimationOnMountbooleanfalseSkip the initial presence animation.
trapFocusbooleantrueTrap focus inside the dialog.
triggerValuestring-Controlled active trigger value.
unmountOnExitbooleantrueUnmount content after the close animation.

DialogTrigger

Opens the dialog. Renders a button.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the trigger.
valuestring-Id for this trigger when several share one dialog.
AttributeDescription
data-slotdialog-trigger
data-scopedialog
data-parttrigger
data-valueThe trigger value
data-state"open" or "closed"
data-currentPresent when this trigger is the active one

DialogContent

Portaled panel. Includes overlay, positioner, and an optional corner close button.

PropTypeDefaultDescription
size"sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl" | "6xl" | "fullscreen""md"Max width of the panel.
showCloseButtonbooleantrueShow the top-end close button.
bottomStickOnMobilebooleantrueStick the panel to the bottom of the viewport below sm.
positionerClassNamestring-Class names on the viewport-fixed positioner.
asChildbooleanfalseMerge onto a single child of the panel.
classNamestring-Class names on the panel.
AttributeDescription
data-slotdialog-content
data-scopedialog
data-partcontent
data-state"open" or "closed"
data-nestedPresent when this dialog is nested
data-has-nestedPresent when this dialog has nested dialogs
CSS variableDescription
--spaceInternal padding. Default --spacing(6).
--layer-indexIndex in the dismissable layer stack.
--nested-layer-countNumber of nested dialogs.

DialogOverlay

Dimmed backdrop. Not rendered when modal={false}. Renders a div.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the overlay.
AttributeDescription
data-slotdialog-overlay
data-scopedialog
data-partbackdrop
data-state"open" or "closed"
CSS variableDescription
--layer-indexIndex in the dismissable layer stack

DialogPositioner

Viewport-fixed wrapper. Usually created by DialogContent. Renders a div.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the positioner.
AttributeDescription
data-slotdialog-positioner
data-scopedialog
data-partpositioner

DialogHeader

Header for title and description. Pass title / description or compose DialogTitle and DialogDescription.

PropTypeDefaultDescription
titlestring-Renders DialogTitle.
descriptionstring-Renders DialogDescription.
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the header.
AttributeDescription
data-slotdialog-header

DialogTitle

Accessible title. Renders an h2. Required for accessibility (use sr-only to hide it visually).

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the title.
AttributeDescription
data-slotdialog-title
data-scopedialog
data-parttitle

DialogDescription

Accessible description. Renders a div.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the description.
AttributeDescription
data-slotdialog-description
data-scopedialog
data-partdescription

DialogBody

Scrollable body. Uses Scroll Area.

PropTypeDefaultDescription
scrollFadebooleanfalseFade the edges of the scroll area.
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the body.
AttributeDescription
data-slotdialog-body

DialogFooter

Footer for actions.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the footer.
AttributeDescription
data-slotdialog-footer

DialogClose

Closes the dialog. Renders a button.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the close control.
AttributeDescription
data-slotdialog-close-trigger
data-scopedialog
data-partclose-trigger

DialogRootProvider

Root alternative that takes the API from useDialog.

PropTypeDefaultDescription
valueUseDialogReturnrequiredReturn value of useDialog().
asChildbooleanfalseMerge onto a single child.
hideMode"display-none" | "activity""display-none"How to hide mounted-but-closed content.
immediateboolean-Apply presence changes immediately.
lazyMountbooleantrueMount content on first open.
onExitComplete() => void-Called when the close animation finishes.
presentboolean-Controlled presence.
skipAnimationOnMountbooleanfalseSkip the initial presence animation.
unmountOnExitbooleantrueUnmount after the close animation.

Pass open, modal, and other machine options to useDialog(), not to DialogRootProvider.

useDialog

Creates the dialog API for DialogRootProvider. Accepts the same options as Dialog except layout-only props.

const dialog = useDialog();
dialog.setOpen(true);

DialogContext / useDialogContext

Render-prop or hook access to dialog state. Use inside Dialog or DialogRootProvider.

PropertyTypeDescription
openbooleanWhether the dialog is open.
setOpen(open: boolean) => voidOpen or close the dialog.
triggerValuestring | nullActive trigger value.
setTriggerValue(value: string | null) => voidSet the active trigger.

DialogContext children: (context) => ReactNode.

Accessibility

Complies with the Dialog WAI-ARIA design pattern. Always include DialogTitle (visually hidden with sr-only if needed).

Keyboard support

KeyDescription
EnterWhen focus is on the trigger, opens the dialog.
TabMove to the next focusable element in the content. Focus is trapped.
Shift + TabMove to the previous focusable element. Focus is trapped.
EscapeClose the dialog and move focus to the trigger or finalFocusEl.