---
title: Dialog
description: A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.
icon: message-square
author:
  name: imskyleen
  url: https://github.com/imskyleen
search:
  keywords: [modal, popup]
related:
  - /alert-dialog
  - /sheet
  - /drawer
  - /credenza
mode: wide
---

<PoweredBy
  packages={[
    { name: 'Base UI', url: 'https://base-ui.com/react/components/dialog' },
    { name: 'Shadcn UI', url: 'https://ui.shadcn.com/docs/components/dialog' },
    { name: 'Motion', url: 'https://motion.dev/' },
    { name: 'Lucide', url: 'https://lucide.dev/' },
  ]}
/>

### Installation

```package-install
npx shadcn@latest add @redpanda/dialog
```

<ComponentPreview name="dialog-demo" />

## When to use

Use this decision tree to determine when to use the Dialog component:

```mermaid
graph TD;
    A[❓ Need modal interaction?] --> B{What type of content?};
    B --> C[✅ Forms/data entry];
    B --> D[✅ Content display/details];
    B --> E[⚠️ Simple confirmations];
    B --> F[⚠️ Mobile responsive needed];

    C --> G[❓ Content complexity?];
    D --> H[ℹ️ Standard dialog implementation];
    E --> I[⚠️ Use AlertDialog instead];
    F --> J[⚠️ Use Credenza instead];

    G --> K[✅ Multi-step forms];
    G --> L[ℹ️ Simple forms];

    K --> M[❓ Animation preferences?];
    L --> M;
    H --> M;

    M --> N[✅ Custom animation direction];
    M --> O[ℹ️ Default top animation];

    N --> P["✅ Set from prop (top/bottom/left/right)"];
    O --> Q[❓ Custom transitions?];
    P --> Q;

    Q --> R[✅ Customize transition timing];
    Q --> S[ℹ️ Use default spring animation];

    R --> T[❓ Backdrop behavior?];
    S --> T;

    T --> U["✅ Click outside to close (default)"];
    T --> V[⚠️ Prevent outside clicks];

    classDef recommended fill:#4ade80,stroke:#22c55e,stroke-width:2px,color:#000;
    classDef discouraged fill:#fbbf24,stroke:#f59e0b,stroke-width:2px,color:#000;
    classDef decision fill:#60a5fa,stroke:#3b82f6,stroke-width:2px,color:#fff;
    classDef neutral fill:#9ca3af,stroke:#6b7280,stroke-width:2px,color:#fff;

    class C,D,K,N,P,R,U recommended;
    class E,F,I,J,V discouraged;
    class A,B,G,M,Q,T decision;
    class H,L,O,S neutral;
```

## Usage

```tsx
<Dialog>
  <DialogTrigger>Open Dialog</DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Title</DialogTitle>
      <DialogDescription>Description</DialogDescription>
    </DialogHeader>
    <p>Dialog Content</p>
    <DialogFooter>
      <button>Close</button>
    </DialogFooter>
  </DialogContent>
</Dialog>
```

## Props

<div className="flex flex-row gap-x-3">
  <ExternalLink href="https://base-ui.com/react/components/dialog" text="Docs" />
  <ExternalLink href="https://base-ui.com/react/components/dialog#api-reference" text="API Reference" />
</div>

## Examples

### Default

<ComponentPreview name="dialog-demo" />

### Delete

<ComponentPreview name="dialog-delete-demo" description="A dialog requiring confirmation to delete a resource." />

### No Header

Skip the header when the dialog body stands on its own — the close button stays at the top-right and the top border is omitted since there's nothing to separate from.

<ComponentPreview name="dialog-no-header-demo" description="A dialog with body and footer only, no header." />

### No Footer

Omit the footer when the dialog is informational and doesn't require explicit actions beyond dismissing.

<ComponentPreview name="dialog-no-footer-demo" description="A dialog with header and body only, no footer." />

### Scrolling content

`DialogContent` caps at `max-h-[85vh]` and `DialogBody` is the scrollable region — long content gets a pinned header/footer with an internal scrollbar for free. When the body overflows, fading top/bottom shadows appear automatically to signal "more above/below"; opt out with `<DialogBody scrollShadow={false}>`.

<ComponentPreview
  name="dialog-inside-scroll-demo"
  description="A dialog whose body exceeds the content cap and scrolls internally. Scroll shadows appear at the top and bottom edges while content is clipped."
/>

### Fixed height

Pass `height="sm" | "md" | "lg" | "xl"` on `DialogContent` to lock the dialog to a stable pixel height (clamped to `85vh`). This is useful for multi-view dialogs — wizards, step flows, gallery → detail — where the container shouldn't reflow as the user navigates between views.

<ComponentPreview
  name="dialog-fixed-height-demo"
  description="A three-step wizard whose container stays the same height across steps, so navigation feels stable instead of jumpy."
/>

### Animated auto height

In the default `height="auto"` mode, the dialog smoothly animates between natural heights when its content changes — toggling sections, async-loaded data, expanding details. This height transition is powered by [Motion](https://motion.dev/). No prop opt-in needed; it's automatic for auto-height dialogs and respects `prefers-reduced-motion`.

<ComponentPreview
  name="dialog-animated-height-demo"
  description="A dialog that animates its height when an expandable section toggles open."
/>

### No Header or Footer

Use body-only dialogs sparingly — without a visible header the dialog still needs an accessible name, so render a `DialogTitle` with `className="sr-only"` for screen readers. The dialog owns close-button clearance for a direct `DialogBody` when no `DialogHeader` is present. Do not add body-padding overrides: the standard headerless inset keeps text clear of the close action. Headered dialogs and dialogs with `showCloseButton={false}` retain their existing body padding.

<ComponentPreview
  name="dialog-body-only-demo"
  description="A body-only dialog with a visually-hidden DialogTitle for accessibility."
/>

### Footer with helper text

When the footer pairs metadata or status text (e.g. "Changes are saved automatically") with a single dismiss action, use `justify="between"` so the text sits on the left and the button on the right. The footer centers items vertically by default, so the text baseline aligns cleanly with the button.

<ComponentPreview
  name="dialog-footer-info-demo"
  description="A dialog whose footer pairs status text with a single dismiss action, vertically centered."
/>

### Component Props

#### DialogContent

| Prop | Type | Default | Description |
| - | - | - | - |
| `size?` | `'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'md'` | Maximum width breakpoint of the dialog. |
| `variant?` | `'standard' \| 'centered' \| 'destructive'` | `'standard'` | Visual treatment of the dialog frame. |
| `height?` | `'auto' \| 'sm' \| 'md' \| 'lg' \| 'xl'` | `'auto'` | Vertical sizing. `auto` grows with content (capped at 85vh) and animates between natural heights when content changes. The fixed sizes lock the dialog to a stable pixel height (each clamped to 85vh) so multi-view dialogs don't reflow between steps. |
| `showCloseButton?` | `boolean` | `true` | Render the top-right close affordance. |
| `showOverlay?` | `boolean` | `true` | Render the dimmed backdrop behind the dialog. |

#### DialogBody

| Prop | Type | Default | Description |
| - | - | - | - |
| `padding?` | `'none' \| 'sm' \| 'md' \| 'lg'` | `'md'` | Padding around the body content. The inner content is wrapped so scroll shadows can sit flush against the body edges — set `padding` here rather than overriding via `className`, which targets the outer scroll container. |
| `spacing?` | `'none' \| 'sm' \| 'md' \| 'lg'` | `'md'` | Vertical gap between direct children of the body. |
| `scrollShadow?` | `boolean` | `true` | Show fading top/bottom shadows when the body overflows, cueing the user that there is more content above or below. |

## Keyboard interactions

| Key                                  | Action                                                                                  |
| ------------------------------------ | --------------------------------------------------------------------------------------- |
| `Enter` / `Space` on `DialogTrigger` | Opens the dialog and moves focus to the first focusable element inside `DialogContent`. |
| `Tab` / `Shift+Tab`                  | Cycles focus within the dialog — focus is trapped while open.                           |
| `Escape`                             | Closes the dialog and restores focus to the trigger.                                    |
| Click outside `DialogContent`        | Closes the dialog (modal scrim dismiss).                                                |

## Accessibility

- Render a `DialogTitle` (or a visually-hidden one) — Base UI enforces this with a development warning. The dialog is
  labelled via `aria-labelledby` pointing at the title.
- Add a `DialogDescription` when the dialog's purpose isn't obvious from the title; it is wired up via
  `aria-describedby`.
- Focus is trapped inside the popup while open and returned to the trigger on close. Background content is marked
  `aria-hidden` and made inert so assistive tech doesn't reach it.
- `onOpenChange` uses Base UI's `(open: boolean, eventDetails) => void` signature. The second argument carries the
  close reason (e.g. `'escape-key'`, `'outside-press'`, `'close-press'`) and is optional to consume — existing
  Radix-style single-argument handlers keep working unchanged.

## Controlled vs uncontrolled

Uncontrolled — let the dialog own open state, react via `onOpenChange`:

```tsx lineNumbers
<Dialog onOpenChange={(open) => console.log('open:', open)}>
  <DialogTrigger>Open</DialogTrigger>
  <DialogContent>{/* ... */}</DialogContent>
</Dialog>
```

Controlled — drive `open` from parent state, e.g. to open the dialog from a menu item or remote action:

```tsx lineNumbers
const [open, setOpen] = useState(false);

<Dialog open={open} onOpenChange={setOpen}>
  <DialogTrigger>Open</DialogTrigger>
  <DialogContent>
    <DialogFooter>
      <Button onClick={() => setOpen(false)}>Done</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>;
```

## Credits

- We use [Base UI](https://base-ui.com/react/components/dialog) for the dialog component.
- We take our inspiration from [Shadcn UI](https://ui.shadcn.com/docs/components/dialog) for the dialog style.
- The animated auto-height transition is powered by [Motion](https://motion.dev/).
