Propel

Dialog

A modal overlay for focused tasks and multi-step flows.

Show code
import { Button } from "@makeplane/propel/components/button";
import {
  Dialog,
  DialogActions,
  DialogBody,
  DialogClose,
  DialogCloseGroup,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogHeading,
  DialogInfo,
  DialogInfoIcon,
  DialogMain,
  DialogProgression,
  DialogTitle,
  DialogTrigger,
} from "@makeplane/propel/components/dialog";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { ChevronLeft, Info, X } from "lucide-react";

/**
 * Default composition: full Modal anatomy — progression, heading, body slot, info helper, and
 * action row.
 */
export default function BasicDemo() {
  return (
    <Dialog>
      <Button
        stretch="auto"
        variant="secondary"
        size="lg"
        render={<DialogTrigger />}
        label="Create cycle"
      />
      <DialogContent>
        <DialogCloseGroup>
          <IconButton
            variant="ghost"
            size="xs"
            aria-label="Close"
            render={<DialogClose />}
            icon={<Icon icon={X} />}
          />
        </DialogCloseGroup>
        <DialogMain>
          <DialogHeader>
            <DialogProgression>
              <IconButton
                variant="ghost"
                size="xs"
                aria-label="Back"
                icon={<Icon icon={ChevronLeft} />}
              />
              Step 1 of 3
            </DialogProgression>
            <DialogHeading>
              <DialogTitle>Create cycle</DialogTitle>
              <DialogDescription>
                Name the cycle and set its date range. You can add work items in the next step.
              </DialogDescription>
            </DialogHeading>
          </DialogHeader>
          <DialogBody>
            <div className="min-h-32 rounded-lg bg-surface-2" />
          </DialogBody>
        </DialogMain>
        <DialogActions>
          <DialogInfo>
            <DialogInfoIcon>
              <Icon icon={Info} />
            </DialogInfoIcon>
            Drafts save automatically
          </DialogInfo>
          <Button
            stretch="auto"
            variant="ghost"
            size="md"
            render={<DialogClose />}
            label="Cancel"
          />
          <Button
            stretch="auto"
            variant="secondary"
            size="md"
            render={<DialogClose />}
            label="Save draft"
          />
          <Button
            stretch="auto"
            variant="primary"
            size="md"
            render={<DialogClose />}
            label="Continue"
          />
        </DialogActions>
      </DialogContent>
    </Dialog>
  );
}

Installation

import {
  Dialog,
  DialogTrigger,
  DialogContent,
  DialogCloseGroup,
  DialogMain,
  DialogHeader,
  DialogProgression,
  DialogHeading,
  DialogTitle,
  DialogDescription,
  DialogBody,
  DialogActions,
  DialogInfo,
  DialogInfoIcon,
  DialogClose,
} from "@makeplane/propel/components/dialog";

Usage

Compose DialogCloseGroup (corner dismiss), DialogMain (progression / heading / optional body slot), and DialogActions (optional DialogInfo + buttons) inside DialogContent. Put DialogDescription in DialogHeading under the title — not in DialogBody. When DialogBody content can overflow, pass tabIndex={0} so the scroll region is keyboard-reachable.

import { Button } from "@makeplane/propel/components/button";
import {
  Dialog,
  DialogActions,
  DialogBody,
  DialogClose,
  DialogCloseGroup,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogHeading,
  DialogInfo,
  DialogInfoIcon,
  DialogMain,
  DialogProgression,
  DialogTitle,
  DialogTrigger,
} from "@makeplane/propel/components/dialog";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { ChevronLeft, Info, X } from "lucide-react";

/**
 * Default composition: full Modal anatomy — progression, heading, body slot, info helper, and
 * action row.
 */
export default function BasicDemo() {
  return (
    <Dialog>
      <Button
        stretch="auto"
        variant="secondary"
        size="lg"
        render={<DialogTrigger />}
        label="Create cycle"
      />
      <DialogContent>
        <DialogCloseGroup>
          <IconButton
            variant="ghost"
            size="xs"
            aria-label="Close"
            render={<DialogClose />}
            icon={<Icon icon={X} />}
          />
        </DialogCloseGroup>
        <DialogMain>
          <DialogHeader>
            <DialogProgression>
              <IconButton
                variant="ghost"
                size="xs"
                aria-label="Back"
                icon={<Icon icon={ChevronLeft} />}
              />
              Step 1 of 3
            </DialogProgression>
            <DialogHeading>
              <DialogTitle>Create cycle</DialogTitle>
              <DialogDescription>
                Name the cycle and set its date range. You can add work items in the next step.
              </DialogDescription>
            </DialogHeading>
          </DialogHeader>
          <DialogBody>
            <div className="min-h-32 rounded-lg bg-surface-2" />
          </DialogBody>
        </DialogMain>
        <DialogActions>
          <DialogInfo>
            <DialogInfoIcon>
              <Icon icon={Info} />
            </DialogInfoIcon>
            Drafts save automatically
          </DialogInfo>
          <Button
            stretch="auto"
            variant="ghost"
            size="md"
            render={<DialogClose />}
            label="Cancel"
          />
          <Button
            stretch="auto"
            variant="secondary"
            size="md"
            render={<DialogClose />}
            label="Save draft"
          />
          <Button
            stretch="auto"
            variant="primary"
            size="md"
            render={<DialogClose />}
            label="Continue"
          />
        </DialogActions>
      </DialogContent>
    </Dialog>
  );
}

Anatomy

Part Role
Dialog Root context (open state, modality)
DialogTrigger / DialogClose Behavior grafts onto Button / IconButton via render
DialogContent Portal + backdrop + viewport + popup (size defaults to md)
DialogCloseGroup Absolute top-end slot for the dismiss IconButton
DialogMain Padded top section (max-height 680 px)
DialogProgression Optional back + step label row
DialogHeader / DialogHeading Progression + title / description stack (6 px gap)
DialogTitle / DialogDescription Accessible name + supporting copy — keep both in Heading
DialogBody Scrollable slot only (forms, lists) — not description
DialogActions Footer with top border
DialogInfo / DialogInfoIcon Optional footer helper

Examples

Sizes

DialogContent’s size sets the dialog width — md (800 px, default) or lg (1000 px). Max height is min(740px, 100dvh − gutter); the body scrolls inside DialogMain when content overflows.

Show code
import { Button } from "@makeplane/propel/components/button";
import {
  Dialog,
  DialogActions,
  DialogBody,
  DialogClose,
  DialogCloseGroup,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogHeading,
  DialogMain,
  DialogTitle,
  DialogTrigger,
} from "@makeplane/propel/components/dialog";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { X } from "lucide-react";

export default function SizesDemo() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Dialog>
        <Button
          stretch="auto"
          variant="secondary"
          size="lg"
          render={<DialogTrigger />}
          label="Medium"
        />
        <DialogContent size="md">
          <DialogCloseGroup>
            <IconButton
              variant="ghost"
              size="xs"
              aria-label="Close"
              render={<DialogClose />}
              icon={<Icon icon={X} />}
            />
          </DialogCloseGroup>
          <DialogMain>
            <DialogHeader>
              <DialogHeading>
                <DialogTitle>Medium dialog</DialogTitle>
                <DialogDescription>800 px wide — standard modal width.</DialogDescription>
              </DialogHeading>
            </DialogHeader>
            <DialogBody>
              <div className="min-h-24 rounded-lg bg-surface-2" />
            </DialogBody>
          </DialogMain>
          <DialogActions>
            <Button
              stretch="auto"
              variant="primary"
              size="md"
              render={<DialogClose />}
              label="Done"
            />
          </DialogActions>
        </DialogContent>
      </Dialog>
      <Dialog>
        <Button
          stretch="auto"
          variant="secondary"
          size="lg"
          render={<DialogTrigger />}
          label="Large"
        />
        <DialogContent size="lg">
          <DialogCloseGroup>
            <IconButton
              variant="ghost"
              size="xs"
              aria-label="Close"
              render={<DialogClose />}
              icon={<Icon icon={X} />}
            />
          </DialogCloseGroup>
          <DialogMain>
            <DialogHeader>
              <DialogHeading>
                <DialogTitle>Large dialog</DialogTitle>
                <DialogDescription>1000 px wide — denser forms and content.</DialogDescription>
              </DialogHeading>
            </DialogHeader>
            <DialogBody>
              <div className="min-h-24 rounded-lg bg-surface-2" />
            </DialogBody>
          </DialogMain>
          <DialogActions>
            <Button
              stretch="auto"
              variant="primary"
              size="md"
              render={<DialogClose />}
              label="Done"
            />
          </DialogActions>
        </DialogContent>
      </Dialog>
    </div>
  );
}

Scrollable body

Tall content scrolls inside DialogBody so the header and footer stay pinned. Pass tabIndex={0} on the body when it can overflow.

Show code
import { Button } from "@makeplane/propel/components/button";
import {
  Dialog,
  DialogActions,
  DialogBody,
  DialogClose,
  DialogCloseGroup,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogHeading,
  DialogMain,
  DialogTitle,
  DialogTrigger,
} from "@makeplane/propel/components/dialog";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { X } from "lucide-react";

const RELEASE_NOTES = Array.from({ length: 12 }, (_, index) => ({
  version: `2.${12 - index}.0`,
  notes:
    "Improved cycle burndown accuracy, faster work-item search indexing, and new keyboard shortcuts for triaging intake items.",
}));

export default function ScrollableBodyDemo() {
  return (
    <Dialog>
      <Button
        stretch="auto"
        variant="secondary"
        size="lg"
        render={<DialogTrigger />}
        label="What's new"
      />
      <DialogContent>
        <DialogCloseGroup>
          <IconButton
            variant="ghost"
            size="xs"
            aria-label="Close"
            render={<DialogClose />}
            icon={<Icon icon={X} />}
          />
        </DialogCloseGroup>
        <DialogMain>
          <DialogHeader>
            <DialogHeading>
              <DialogTitle>What&apos;s new</DialogTitle>
              <DialogDescription>
                Everything that shipped in the last twelve releases.
              </DialogDescription>
            </DialogHeading>
          </DialogHeader>
          {/* A scrollable region must be keyboard-reachable (axe scrollable-region-focusable). */}
          <DialogBody tabIndex={0}>
            <div className="flex flex-col gap-4">
              {RELEASE_NOTES.map((release) => (
                <div key={release.version} className="flex flex-col gap-1">
                  <h3 className="text-body-sm-medium text-primary">Version {release.version}</h3>
                  <p className="text-body-sm-regular text-secondary">{release.notes}</p>
                </div>
              ))}
            </div>
          </DialogBody>
        </DialogMain>
        <DialogActions>
          <Button
            stretch="auto"
            variant="primary"
            size="md"
            render={<DialogClose />}
            label="Got it"
          />
        </DialogActions>
      </DialogContent>
    </Dialog>
  );
}

Confirmation

Title, description, and cancel / confirm actions — a short task without a body slot.

Show code
import { Button } from "@makeplane/propel/components/button";
import {
  Dialog,
  DialogActions,
  DialogClose,
  DialogCloseGroup,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogHeading,
  DialogMain,
  DialogTitle,
  DialogTrigger,
} from "@makeplane/propel/components/dialog";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { X } from "lucide-react";

export default function ConfirmationDemo() {
  return (
    <Dialog>
      <Button
        stretch="auto"
        variant="secondary"
        size="lg"
        render={<DialogTrigger />}
        label="Delete project"
      />
      <DialogContent>
        <DialogCloseGroup>
          <IconButton
            variant="ghost"
            size="xs"
            aria-label="Close"
            render={<DialogClose />}
            icon={<Icon icon={X} />}
          />
        </DialogCloseGroup>
        <DialogMain>
          <DialogHeader>
            <DialogHeading>
              <DialogTitle>Delete project</DialogTitle>
              <DialogDescription>
                This permanently removes the project and all of its work items. This action
                can&apos;t be undone.
              </DialogDescription>
            </DialogHeading>
          </DialogHeader>
        </DialogMain>
        <DialogActions>
          <Button
            stretch="auto"
            variant="secondary"
            size="md"
            render={<DialogClose />}
            label="Cancel"
          />
          <Button
            stretch="auto"
            variant="danger"
            size="md"
            render={<DialogClose />}
            label="Delete"
          />
        </DialogActions>
      </DialogContent>
    </Dialog>
  );
}

Non-dismissable

With disablePointerDismissal on the root, a backdrop click no longer closes the dialog — it dismisses only via an explicit DialogClose or Escape.

Show code
import { Button } from "@makeplane/propel/components/button";
import {
  Dialog,
  DialogActions,
  DialogClose,
  DialogCloseGroup,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogHeading,
  DialogMain,
  DialogTitle,
  DialogTrigger,
} from "@makeplane/propel/components/dialog";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { X } from "lucide-react";

export default function NonDismissableDemo() {
  return (
    <Dialog disablePointerDismissal>
      <Button
        stretch="auto"
        variant="secondary"
        size="lg"
        render={<DialogTrigger />}
        label="Open locked dialog"
      />
      <DialogContent>
        <DialogCloseGroup>
          <IconButton
            variant="ghost"
            size="xs"
            aria-label="Close"
            render={<DialogClose />}
            icon={<Icon icon={X} />}
          />
        </DialogCloseGroup>
        <DialogMain>
          <DialogHeader>
            <DialogHeading>
              <DialogTitle>Unsaved changes</DialogTitle>
              <DialogDescription>
                Choose an action — clicking outside won&apos;t dismiss.
              </DialogDescription>
            </DialogHeading>
          </DialogHeader>
        </DialogMain>
        <DialogActions>
          <Button
            stretch="auto"
            variant="secondary"
            size="md"
            render={<DialogClose />}
            label="Discard"
          />
        </DialogActions>
      </DialogContent>
    </Dialog>
  );
}

Nested dialogs

A DialogTrigger inside a dialog stacks a second dialog above it. Escape unwinds one level at a time.

Show code
import { Button } from "@makeplane/propel/components/button";
import {
  Dialog,
  DialogActions,
  DialogClose,
  DialogCloseGroup,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogHeading,
  DialogMain,
  DialogTitle,
  DialogTrigger,
} from "@makeplane/propel/components/dialog";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { X } from "lucide-react";

export default function NestedDialogsDemo() {
  return (
    <Dialog>
      <Button
        stretch="auto"
        variant="secondary"
        size="lg"
        render={<DialogTrigger />}
        label="Invite teammates"
      />
      <DialogContent>
        <DialogCloseGroup>
          <IconButton
            variant="ghost"
            size="xs"
            aria-label="Close"
            render={<DialogClose />}
            icon={<Icon icon={X} />}
          />
        </DialogCloseGroup>
        <DialogMain>
          <DialogHeader>
            <DialogHeading>
              <DialogTitle>Invite teammates</DialogTitle>
              <DialogDescription>
                Invited members join with the Member role. Need something more specific? Create a
                custom role first.
              </DialogDescription>
            </DialogHeading>
          </DialogHeader>
        </DialogMain>
        <DialogActions>
          <Dialog>
            <Button
              stretch="auto"
              variant="secondary"
              size="md"
              render={<DialogTrigger />}
              label="Create custom role"
            />
            <DialogContent>
              <DialogCloseGroup>
                <IconButton
                  variant="ghost"
                  size="xs"
                  aria-label="Close"
                  render={<DialogClose />}
                  icon={<Icon icon={X} />}
                />
              </DialogCloseGroup>
              <DialogMain>
                <DialogHeader>
                  <DialogHeading>
                    <DialogTitle>Create custom role</DialogTitle>
                    <DialogDescription>
                      Custom roles scope what invited members can see and edit.
                    </DialogDescription>
                  </DialogHeading>
                </DialogHeader>
              </DialogMain>
              <DialogActions>
                <Button
                  stretch="auto"
                  variant="secondary"
                  size="md"
                  render={<DialogClose />}
                  label="Back"
                />
              </DialogActions>
            </DialogContent>
          </Dialog>
          <Button
            stretch="auto"
            variant="primary"
            size="md"
            render={<DialogClose />}
            label="Send invites"
          />
        </DialogActions>
      </DialogContent>
    </Dialog>
  );
}

Open from a menu

Choosing a menu item closes the menu, so the dialog lives outside it and is opened imperatively via controlled open / onOpenChange.

Show code
import { Button } from "@makeplane/propel/components/button";
import {
  Dialog,
  DialogActions,
  DialogClose,
  DialogCloseGroup,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogHeading,
  DialogMain,
  DialogTitle,
} from "@makeplane/propel/components/dialog";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import {
  Menu,
  MenuContent,
  MenuItem,
  MenuSeparator,
  MenuTrigger,
} from "@makeplane/propel/components/menu";
import { Link2, Pencil, Trash2, X } from "lucide-react";
import * as React from "react";

export default function OpenFromMenuDemo() {
  const [open, setOpen] = React.useState(false);
  return (
    <>
      <Menu>
        <Button
          stretch="auto"
          variant="secondary"
          size="lg"
          render={<MenuTrigger />}
          label="Project options"
        />
        <MenuContent>
          <MenuItem icon={<Icon icon={Pencil} tint="secondary" />} label="Edit" />
          <MenuItem icon={<Icon icon={Link2} tint="secondary" />} label="Copy link" />
          <MenuSeparator />
          <MenuItem
            variant="danger"
            icon={<Icon icon={Trash2} />}
            onClick={() => setOpen(true)}
            label="Delete project…"
          />
        </MenuContent>
      </Menu>
      <Dialog open={open} onOpenChange={setOpen}>
        <DialogContent>
          <DialogCloseGroup>
            <IconButton
              variant="ghost"
              size="xs"
              aria-label="Close"
              render={<DialogClose />}
              icon={<Icon icon={X} />}
            />
          </DialogCloseGroup>
          <DialogMain>
            <DialogHeader>
              <DialogHeading>
                <DialogTitle>Delete project</DialogTitle>
                <DialogDescription>
                  This permanently removes the project and all of its work items. This action
                  can&apos;t be undone.
                </DialogDescription>
              </DialogHeading>
            </DialogHeader>
          </DialogMain>
          <DialogActions>
            <Button
              stretch="auto"
              variant="secondary"
              size="md"
              render={<DialogClose />}
              label="Cancel"
            />
            <Button
              stretch="auto"
              variant="danger"
              size="md"
              render={<DialogClose />}
              label="Delete"
            />
          </DialogActions>
        </DialogContent>
      </Dialog>
    </>
  );
}

Detached triggers

createDialogHandle() links triggers to a Dialog defined elsewhere in the tree, so several launch points share one dialog without lifting state.

Show code
import { Button } from "@makeplane/propel/components/button";
import {
  createDialogHandle,
  Dialog,
  DialogActions,
  DialogClose,
  DialogCloseGroup,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogHeading,
  DialogMain,
  DialogTitle,
  DialogTrigger,
} from "@makeplane/propel/components/dialog";
import { Icon } from "@makeplane/propel/components/icon";
import { IconButton } from "@makeplane/propel/components/icon-button";
import { X } from "lucide-react";

// A handle links triggers that live far from the dialog they open, so several
// launch points can share one dialog without hoisting controlled state.
const shortcutsDialog = createDialogHandle();

export default function DetachedTriggersDemo() {
  return (
    <div className="flex items-center gap-2">
      <Button
        stretch="auto"
        variant="secondary"
        size="lg"
        render={<DialogTrigger handle={shortcutsDialog} />}
        label="Keyboard shortcuts"
      />
      <Button
        stretch="auto"
        variant="ghost"
        size="lg"
        render={<DialogTrigger handle={shortcutsDialog} />}
        label="Help"
      />
      <Dialog handle={shortcutsDialog}>
        <DialogContent>
          <DialogCloseGroup>
            <IconButton
              variant="ghost"
              size="xs"
              aria-label="Close"
              render={<DialogClose />}
              icon={<Icon icon={X} />}
            />
          </DialogCloseGroup>
          <DialogMain>
            <DialogHeader>
              <DialogHeading>
                <DialogTitle>Keyboard shortcuts</DialogTitle>
                <DialogDescription>
                  Press <kbd>C</kbd> to create a work item, <kbd>/</kbd> to search, and <kbd>?</kbd>{" "}
                  to reopen this list from anywhere.
                </DialogDescription>
              </DialogHeading>
            </DialogHeader>
          </DialogMain>
          <DialogActions>
            <Button
              stretch="auto"
              variant="primary"
              size="md"
              render={<DialogClose />}
              label="Done"
            />
          </DialogActions>
        </DialogContent>
      </Dialog>
    </div>
  );
}

API Reference

Dialog

The dialog Root — Base UI's context/state provider (renders no element of its own). Modal by default; pass `disablePointerDismissal` to keep it open on outside clicks. A behavior-only role, so it lives in `components` (rules 1a, 2); the styled parts live in `elements/dialog` and are grafted onto Base UI behavior here.

PropTypeDefaultDescription
childrenReactNode | PayloadChildRenderFunction<unknown>The content of the dialog. This can be a regular React node or a render function that receives the `payload` of the active trigger.

DialogTrigger

The behavior that opens the dialog. Use as the `render` target of a `Button` so the styled primitive's look wins via render-composition: ```tsx <Button variant="secondary" size="lg" label="Open dialog" render={<DialogTrigger />} />; ``` Base UI manages `aria-haspopup`/`aria-expanded` and focus restoration when the dialog closes. Maps 1:1 to `Dialog.Trigger`.

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogTriggerState>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.

DialogContent

Convenience that composes the dialog overlay boilerplate — portal, shared `Backdrop`, centering `DialogViewport`, and the centered `DialogPopup` — so a consumer only writes the trigger and the popup body. Each Base UI behavior part grafts onto its styled element via `render`.

PropTypeDefaultDescription
size"md" | "lg""md"Dialog width: `md` (800 px) or `lg` (1000 px).

DialogTitle

The accessible title for the dialog: Base UI's `Dialog.Title` behavior (links to the popup via `aria-labelledby`) grafted onto the dialog-owned title chrome (`text-h5-medium`).

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogTitleState>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

The supporting description for the dialog: Base UI's `Dialog.Description` behavior (links to the popup via `aria-describedby`) grafted onto the dialog-owned description chrome (`text-body-xs` tertiary).

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogDescriptionState>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.

DialogClose

The behavior that closes the dialog when activated. Use as the `render` target of a `Button` or `IconButton` so the styled primitive's look wins via render-composition. Corner dismiss belongs in `DialogCloseGroup`: ```tsx <DialogCloseGroup> <IconButton variant="ghost" size="xs" aria-label={closeLabel} icon={<Icon icon={X} />} render={<DialogClose />} /> </DialogCloseGroup>; ``` Maps 1:1 to `Dialog.Close`.

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, DialogCloseState>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.

DialogMain

The padded top section of the dialog popup: stacks optional `DialogProgression`, `DialogHeader` / `DialogHeading`, and the scrollable `DialogBody`. Owns the equal 16 px gutters (Figma) and the 680 px max height under the 740 px popup cap. Footer visibility comes from flex (`min-h-0` here, `shrink-0` on `DialogActions`), not a reserved footer offset. The absolute close overlays the top-end — it does not widen the end gutter.

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>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.

DialogCloseGroup

Positions the dismiss control at the popup's top inline-end corner. Hold an `IconButton` (size `xs`, variant `ghost`) with `render={<DialogClose />}` so the button chrome wins and Base UI supplies close behavior.

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>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.

DialogProgression

Optional step row at the top of `DialogHeader`: compose a back `IconButton` (size `xs`, variant `ghost`) and the step label as children. Caption-md tertiary chrome is baked in. Laid out in normal flow (not Figma's absolute hang into the gutter) — the glyph sits on the Main padding edge; consumers supply the back handler.

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>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.

DialogHeader

The top block inside `DialogMain`: stacks an optional `DialogProgression` over a `DialogHeading` (title + optional description). Dismiss lives in `DialogCloseGroup` on the popup, not here — this part carries extra end padding so progression/title clear the absolute close without widening Main/Body gutters. Full-width header controls (e.g. a command-palette search field) skip this part and sit directly in `DialogMain` so they keep equal side gutters.

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>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.

DialogHeading

The heading block: stacks the `DialogTitle` over an optional `DialogDescription` with a tight gap. Sit inside `DialogHeader` (optionally under `DialogProgression`). Keep supporting copy here — not in `DialogBody` — so title/description use the 6 px heading rhythm.

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>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.

DialogBody

The scrollable main slot inside `DialogMain`. Grows to fill available space and scrolls vertically when content exceeds the popup's max-height. Pass form fields, lists, or other slot content — keep `DialogDescription` in `DialogHeading`. When the body can overflow, pass `tabIndex={0}` so the scroll region is keyboard-reachable (axe `scrollable-region-focusable`).

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>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.

DialogActions

The footer of the dialog popup. Top border separates it from `DialogMain`. Compose an optional `DialogInfo` (flexes to the inline-start) followed by action buttons (ghost / secondary / primary) with a 12 px gap.

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>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.

DialogInfo

Optional footer helper at the inline-start of `DialogActions`. Compose a `DialogInfoIcon` and the helper label as children. Flexes to push action buttons to the inline-end.

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>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.

DialogInfoIcon

Decorative 14 px icon slot inside `DialogInfo`. Bakes `aria-hidden` and sizes its child via the shared node-slot. Pass a lucide glyph (or `<Icon icon={…} />`) as children.

PropTypeDefaultDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}>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.