Dialog
A modal overlay for focused tasks and confirmations.
Basic
Show codeHide code
import { Button } from "@makeplane/propel/components/button";
import {
Dialog,
DialogActions,
DialogBody,
DialogClose,
DialogContent,
DialogDescription,
DialogHeader,
DialogHeading,
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 BasicDemo() {
return (
<Dialog>
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="xl"
render={<DialogTrigger />}
label="Delete project"
/>
<DialogContent magnitude="sm">
<DialogHeader>
<DialogHeading>
<DialogTitle>Delete project</DialogTitle>
</DialogHeading>
<IconButton
prominence="ghost"
tone="neutral"
magnitude="lg"
aria-label="Close"
render={<DialogClose />}
icon={<Icon icon={X} />}
/>
</DialogHeader>
<DialogBody>
<DialogDescription>
This permanently removes the project and all of its work items. This action can't
be undone.
</DialogDescription>
</DialogBody>
<DialogActions>
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="xl"
render={<DialogClose />}
label="Cancel"
/>
<Button
sizing="hug"
prominence="primary"
tone="danger"
magnitude="xl"
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 codeHide code
import { Button } from "@makeplane/propel/components/button";
import {
Dialog,
DialogActions,
DialogBody,
DialogClose,
DialogContent,
DialogDescription,
DialogHeader,
DialogHeading,
DialogTitle,
DialogTrigger,
} from "@makeplane/propel/components/dialog";
export default function NonDismissableDemo() {
return (
<Dialog disablePointerDismissal>
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="xl"
render={<DialogTrigger />}
label="Open locked dialog"
/>
<DialogContent magnitude="sm">
<DialogHeader>
<DialogHeading>
<DialogTitle>Unsaved changes</DialogTitle>
</DialogHeading>
</DialogHeader>
<DialogBody>
<DialogDescription>
Choose an action — clicking outside won't dismiss.
</DialogDescription>
</DialogBody>
<DialogActions>
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="xl"
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 codeHide code
import { Button } from "@makeplane/propel/components/button";
import {
Dialog,
DialogActions,
DialogBody,
DialogClose,
DialogContent,
DialogDescription,
DialogHeader,
DialogHeading,
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
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="xl"
render={<DialogTrigger />}
label="Invite teammates"
/>
<DialogContent magnitude="md">
<DialogHeader>
<DialogHeading>
<DialogTitle>Invite teammates</DialogTitle>
</DialogHeading>
<IconButton
prominence="ghost"
tone="neutral"
magnitude="lg"
aria-label="Close"
render={<DialogClose />}
icon={<Icon icon={X} />}
/>
</DialogHeader>
<DialogBody>
<DialogDescription>
Invited members join with the Member role. Need something more specific? Create a custom
role first.
</DialogDescription>
</DialogBody>
<DialogActions>
<Dialog>
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="xl"
render={<DialogTrigger />}
label="Create custom role"
/>
<DialogContent magnitude="sm">
<DialogHeader>
<DialogHeading>
<DialogTitle>Create custom role</DialogTitle>
</DialogHeading>
</DialogHeader>
<DialogBody>
<DialogDescription>
Custom roles scope what invited members can see and edit.
</DialogDescription>
</DialogBody>
<DialogActions>
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="xl"
render={<DialogClose />}
label="Back"
/>
</DialogActions>
</DialogContent>
</Dialog>
<Button
sizing="hug"
prominence="primary"
tone="neutral"
magnitude="xl"
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 codeHide code
import { Button } from "@makeplane/propel/components/button";
import {
Dialog,
DialogActions,
DialogBody,
DialogClose,
DialogContent,
DialogDescription,
DialogHeader,
DialogHeading,
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
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="xl"
render={<MenuTrigger />}
label="Project options"
/>
<MenuContent sizing="sm">
<MenuItem icon={<Icon icon={Pencil} tint="secondary" />} label="Edit" />
<MenuItem icon={<Icon icon={Link2} tint="secondary" />} label="Copy link" />
<MenuSeparator />
<MenuItem
tone="danger"
icon={<Icon icon={Trash2} />}
onClick={() => setOpen(true)}
label="Delete project…"
/>
</MenuContent>
</Menu>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent magnitude="sm">
<DialogHeader>
<DialogHeading>
<DialogTitle>Delete project</DialogTitle>
</DialogHeading>
<IconButton
prominence="ghost"
tone="neutral"
magnitude="lg"
aria-label="Close"
render={<DialogClose />}
icon={<Icon icon={X} />}
/>
</DialogHeader>
<DialogBody>
<DialogDescription>
This permanently removes the project and all of its work items. This action can't
be undone.
</DialogDescription>
</DialogBody>
<DialogActions>
<Button
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="xl"
render={<DialogClose />}
label="Cancel"
/>
<Button
sizing="hug"
prominence="primary"
tone="danger"
magnitude="xl"
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 codeHide code
import { Button } from "@makeplane/propel/components/button";
import {
createDialogHandle,
Dialog,
DialogActions,
DialogBody,
DialogClose,
DialogContent,
DialogDescription,
DialogHeader,
DialogHeading,
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
sizing="hug"
prominence="secondary"
tone="neutral"
magnitude="xl"
render={<DialogTrigger handle={shortcutsDialog} />}
label="Keyboard shortcuts"
/>
<Button
sizing="hug"
prominence="ghost"
tone="neutral"
magnitude="xl"
render={<DialogTrigger handle={shortcutsDialog} />}
label="Help"
/>
<Dialog handle={shortcutsDialog}>
<DialogContent magnitude="sm">
<DialogHeader>
<DialogHeading>
<DialogTitle>Keyboard shortcuts</DialogTitle>
</DialogHeading>
<IconButton
prominence="ghost"
tone="neutral"
magnitude="lg"
aria-label="Close"
render={<DialogClose />}
icon={<Icon icon={X} />}
/>
</DialogHeader>
<DialogBody>
<DialogDescription>
Press <kbd>C</kbd> to create a work item, <kbd>/</kbd> to search, and <kbd>?</kbd> to
reopen this list from anywhere.
</DialogDescription>
</DialogBody>
<DialogActions>
<Button
sizing="hug"
prominence="primary"
tone="neutral"
magnitude="xl"
render={<DialogClose />}
label="Done"
/>
</DialogActions>
</DialogContent>
</Dialog>
</div>
);
}Installation
import { Dialog, DialogTrigger, DialogContent } from "@makeplane/propel/components/dialog";
Props
Dialog
| Prop | Type | Required | Description |
|---|---|---|---|
| children | ReactNode | PayloadChildRenderFunction<unknown> | 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. |
DialogContent
| Prop | Type | Required | Description |
|---|---|---|---|
| render | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}> | 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. |
| magnitude | "sm" | "md" | "lg" | Yes |