---
id: "packages/active-ui/dialog"
title: "Dialog"
description: "A window over the page that holds the reader's attention until it closes, with focus kept inside."
url: "/packages/active-ui/dialog"
locale: "en"
revision: "1de87bd2bfc5209d"
---

# Dialog

A dialog interrupts: it asks for a decision or a short task, such as renaming an item, before the
reader returns to the page. The page behind it cannot be used while it is open. For a choice that
cannot be undone, use [ConfirmActionDialog](/packages/active-ui/confirm-action-dialog); for
content the reader consults beside the page, use a sheet or a popover.

**Example: A dialog with a trigger**

Client: Runs entirely in your browser. Source: `examples/ui/dialog-basic.tsx`.

```tsx
// apps/docs/examples/ui/dialog-basic.tsx
'use client';

import {Button} from '@workspace/active-ui/components/primitives/button';
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@workspace/active-ui/components/primitives/dialog';
import {
  Field,
  FieldLabel,
} from '@workspace/active-ui/components/primitives/field';
import {Input} from '@workspace/active-ui/components/primitives/input';

/**
 * A trigger, a title and description, content, and a footer. The close
 * button at the top right has no visible text, so `closeLabel` gives it its
 * accessible name; the package does not supply one.
 */
export default function DialogBasic() {
  return (
    <Dialog>
      <DialogTrigger render={<Button variant="outline" />}>
        Rename scheme
      </DialogTrigger>
      <DialogContent closeLabel="Close">
        <DialogHeader>
          <DialogTitle>Rename scheme</DialogTitle>
          <DialogDescription>
            The new name appears everywhere the scheme is listed.
          </DialogDescription>
        </DialogHeader>
        <Field>
          <FieldLabel htmlFor="dialog-scheme-name">Name</FieldLabel>
          <Input id="dialog-scheme-name" defaultValue="Basic scheme 2026" />
        </Field>
        <DialogFooter>
          <DialogClose render={<Button variant="outline" />}>
            Cancel
          </DialogClose>
          <DialogClose render={<Button />}>Save</DialogClose>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}
```

## Usage

```tsx
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@workspace/active-ui/components/primitives/dialog';

<Dialog>
  <DialogTrigger render={<Button variant="outline" />}>Open</DialogTrigger>
  <DialogContent closeLabel="Close">
    <DialogHeader>
      <DialogTitle>Title</DialogTitle>
      <DialogDescription>What this dialog is for.</DialogDescription>
    </DialogHeader>
  </DialogContent>
</Dialog>;
```

## Anatomy

- `Dialog`: the state. Uncontrolled by default; controlled with `open` and `onOpenChange`.
- `DialogTrigger`: the element that opens it. `render` makes your own button the trigger.
- `DialogContent`: the window, with the overlay behind it and a close button in the corner.
- `DialogHeader` with `DialogTitle` and `DialogDescription`: the title names the dialog for
  assistive technology; the description is read with it.
- `DialogFooter`: the actions. `DialogClose` closes the dialog from any element.

## Controlled, without the corner button

**Example: Controlled, without the corner button**

Client: Runs entirely in your browser. Source: `examples/ui/dialog-controlled.tsx`.

```tsx
// apps/docs/examples/ui/dialog-controlled.tsx
'use client';

import {useState} from 'react';

import {Button} from '@workspace/active-ui/components/primitives/button';
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@workspace/active-ui/components/primitives/dialog';

/**
 * Open state owned by the application: `open` and `onOpenChange`. Without
 * the corner button (`showCloseButton={false}`), the dialog still closes
 * with Escape, a click outside it, or the buttons in the footer.
 */
export default function DialogControlled() {
  const [open, setOpen] = useState(false);
  const [last, setLast] = useState('none yet');
  const close = (reason: string) => {
    setLast(reason);
    setOpen(false);
  };
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button variant="outline" onClick={() => setOpen(true)}>
        Show the terms
      </Button>
      <span className="text-muted-foreground text-sm" role="status">
        Last choice: {last}
      </span>
      <Dialog
        open={open}
        onOpenChange={(next) => {
          if (!next) close('dismissed');
        }}
      >
        <DialogContent showCloseButton={false}>
          <DialogHeader>
            <DialogTitle>Terms of use</DialogTitle>
            <DialogDescription>
              Read the terms before you continue.
            </DialogDescription>
          </DialogHeader>
          <p>
            The service is provided as is. You keep the rights to the data you
            enter, and you can export it at any time.
          </p>
          <DialogFooter>
            <Button variant="outline" onClick={() => close('declined')}>
              Decline
            </Button>
            <Button onClick={() => close('accepted')}>Accept</Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </div>
  );
}
```

Pass `open` and `onOpenChange` when your application decides when the dialog opens or must know
how it closed. `showCloseButton={false}` removes the corner button; Escape and a click outside
still close the dialog, and `onOpenChange(false)` reports it.

## What your application provides

- `closeLabel`: the accessible name of the corner button. It is required while the button is shown,
  and not allowed with `showCloseButton={false}`; the types enforce both.
- The title and description, and the labels of the actions.
- Any work the dialog's actions start, and the open state when the dialog is controlled.

## Accessibility and keyboard

- Opening moves the focus into the dialog; `Tab` and `Shift+Tab` stay inside it.
- `Escape` closes it, and the focus returns to the element that opened it.
- The dialog is named by its title (`aria-labelledby`) and described by its description.
- The page behind it is inert while it is open.

## API

### Dialog

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `actionsRef` | `RefObject<DialogRootActions \| null>` | no | A ref to imperative actions. - `unmount`: Ends the closing phase of the dialog after an externally controlled closing animation finishes. Call `preventUnmountOnClose()` in `onOpenChange` first, otherwise the dialog completes closing on its own. Whether it leaves the DOM is decided by `keepMounted` on the portal. - `close`: Closes the dialog imperatively when called. |
| `children` | `PayloadChildRenderFunction<unknown> \| ReactNode` | no | The content of the dialog. This can be a regular React node or a render function that receives the `payload` of the active trigger. |
| `defaultOpen` | `boolean` | no | Whether the dialog is initially open.  To render a controlled dialog, use the `open` prop instead. |
| `defaultTriggerId` | `string \| null` | no | ID of the trigger that the dialog is associated with. This is useful in conjunction with the `defaultOpen` prop to create an initially open dialog. |
| `disablePointerDismissal` | `boolean` | no | Whether to prevent the dialog from closing on outside presses. For non-modal dialogs, this also prevents the dialog from closing when focus moves outside of it. |
| `handle` | `DialogHandle<unknown>` | no | A handle to associate the dialog with a trigger. If specified, allows external triggers to control the dialog's open state. Can be created with the Dialog.createHandle() method. |
| `modal` | `"trap-focus" \| boolean` | no | Determines if the dialog enters a modal state when open. - `true`: user interaction is limited to just the dialog: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled. - `false`: user interaction with the rest of the document is allowed. - `'trap-focus'`: focus is trapped inside the dialog, but document page scroll is not locked and pointer interactions outside of it remain enabled.  When `modal` is `true` or `'trap-focus'`, render `<Dialog.Close>` inside `<Dialog.Popup>` so touch screen readers can escape the popup. |
| `onOpenChange` | `((open: boolean, eventDetails: DialogRoot.ChangeEventDetails) => void)` | no | Event handler called when the dialog is opened or closed. |
| `onOpenChangeComplete` | `((open: boolean) => void)` | no | Event handler called after any animations complete when the dialog is opened or closed. |
| `open` | `boolean` | no | Whether the dialog is currently open. |
| `triggerId` | `string \| null` | no | ID of the trigger that the dialog is associated with. This is useful in conjunction with the `open` prop to create a controlled dialog. There's no need to specify this prop when the dialog is uncontrolled (that is, when the `open` prop is not set). |

### DialogContent

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `closeLabel` | `string` | no | The accessible name of the close button in the corner, in the reader's language. Required while the button is shown; not allowed with `showCloseButton={false}`. |
| `showCloseButton` | `boolean` | no | Whether the close button in the corner is shown. Escape and a click outside close the dialog either way. |
| `finalFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((closeType: InteractionType) => boolean \| HTMLElement \| null \| void)` | no | Determines the element to focus when the dialog is closed.  - `false`: Do not move focus. - `true`: Move focus based on the default behavior (trigger or previously focused element). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`). Return an element to focus, `true` to use the default behavior, `null` to fall back to the default behavior, or `false`/`undefined` to do nothing. |
| `initialFocus` | `boolean \| RefObject<HTMLElement \| null> \| ((openType: InteractionType) => boolean \| HTMLElement \| null \| void)` | no | Determines the element to focus when the dialog is opened. By default, focus moves to the first tabbable element inside the popup, except when the dialog is opened by touch — then the popup itself is focused to avoid opening the virtual keyboard.  - `false`: Do not move focus. - `true`: Move focus based on the default behavior (first tabbable element or popup). - `RefObject`: Move focus to the ref element. - `function`: Called with the interaction type (`mouse`, `touch`, `pen`, or `keyboard`). Return an element to focus, `true` to use the default behavior, `null` to fall back to the default behavior, or `false`/`undefined` to do nothing. |
| `render` | `ComponentRenderFn<HTMLProps, DialogPopupState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

### Other parts

`DialogTrigger` and `DialogClose` take Base UI's `render` prop to use your own element.
`DialogFooter` can render its own close button with `showCloseButton` and a visible
`closeLabel`.

**DialogClose**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `nativeButton` | `boolean` | no | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `render` | `ComponentRenderFn<HTMLProps, DialogCloseState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

**DialogDescription**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, DialogDescriptionState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

**DialogFooter**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `closeLabel` | `string` | no | The visible text of that button; required when it is shown. |
| `showCloseButton` | `boolean` | no | Whether the footer ends with a button that closes the dialog. |

**DialogHeader**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |

DialogHeader has no props of its own.

**DialogOverlay**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `forceRender` | `boolean` | no | Whether the backdrop is forced to render even when nested. |
| `render` | `ComponentRenderFn<HTMLProps, DialogBackdropState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

**DialogPortal**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `container` | `HTMLElement \| RefObject<HTMLElement \| ShadowRoot \| null> \| ShadowRoot \| null` | no | A parent element to render the portal element into. |
| `keepMounted` | `boolean` | no | Whether to keep the portal mounted in the DOM while the popup is hidden. |
| `render` | `ComponentRenderFn<HTMLProps, DialogPortalState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

**DialogTitle**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `render` | `ComponentRenderFn<HTMLProps, DialogTitleState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

**DialogTrigger**

| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| `handle` | `DialogHandle<unknown>` | no | A handle to associate the trigger with a dialog. Can be created with the Dialog.createHandle() method. |
| `id` | `string` | no | ID of the trigger. In addition to being forwarded to the rendered element, it is also used to specify the active trigger for the dialog in controlled mode (with the DialogRoot `triggerId` prop). |
| `nativeButton` | `boolean` | no | Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`). |
| `payload` | `unknown` | no | A payload to pass to the dialog when it is opened. |
| `render` | `ComponentRenderFn<HTMLProps, DialogTriggerState> \| ReactElement<unknown, string \| JSXElementConstructor<any>>` | no | Allows you to replace the component's HTML element with a different tag, or compose it with another component.  Accepts a `ReactElement` or a function that returns the element to render. |

> [!NOTE]
> **Based on shadcn/ui**
>
> This page adapts the Dialog documentation of shadcn/ui (MIT licence) to this
> package; see [Third-party notices](/third-party-notices).
