Dialog
A modal overlay for focused tasks and multi-step flows.
Show codeHide code
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 codeHide code
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 codeHide code
Confirmation
Title, description, and cancel / confirm actions — a short task without a body slot.
Show codeHide code
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 codeHide code
Nested dialogs
A DialogTrigger inside a dialog stacks a second dialog above it. Escape unwinds one level at a
time.
Show codeHide code
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 codeHide code
Detached triggers
createDialogHandle() links triggers to a Dialog defined elsewhere in the tree, so several launch
points share one dialog without lifting state.
Show codeHide code
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.
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`.
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`.
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`).
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).
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`.
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.
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.
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.
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.
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.
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`).
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.
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.
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.