import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { toast } from "@/components/ui/toast";
export default function DialogEditProfile() {
return (
<Dialog>
<DialogTrigger render={<Button variant="secondary" />}>Edit profile</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>Changes are visible to everyone at Acme.</DialogDescription>
</DialogHeader>
<FieldGroup className="gap-3">
<Field>
<FieldLabel htmlFor="profile-name">Full name</FieldLabel>
<Input id="profile-name" defaultValue="Ada Park" />
</Field>
<Field>
<FieldLabel htmlFor="profile-title">Job title</FieldLabel>
<Input id="profile-title" defaultValue="Product designer" />
</Field>
</FieldGroup>
<DialogFooter>
<DialogClose render={<Button variant="secondary" />}>Cancel</DialogClose>
<DialogClose
render={<Button variant="primary" />}
onClick={() => toast.add({ title: "Profile updated", type: "success" })}
>
Save changes
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
}- Add the registryOnce per project
pnpm dlx shadcn@latest registry add @jarvis=https://ui.jarv.is/r/{name}.json - Add dialog
pnpm dlx shadcn@latest add @jarvis/dialog
Usage
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
export function RenameDialog() {
return (
<Dialog>
<DialogTrigger render={<Button variant="secondary" />}>Rename</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Rename project</DialogTitle>
<DialogDescription>The new name is visible to everyone.</DialogDescription>
</DialogHeader>
<p>…</p>
<DialogFooter>
<DialogClose render={<Button variant="secondary" />}>Cancel</DialogClose>
<DialogClose render={<Button variant="primary" />}>Save</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
}Pass a Button to render on the trigger and close buttons. Always include a DialogTitle.
To open or close the dialog from your own code, control it with open and onOpenChange.
Composition
Examples
Wide
Set a wider max-w on DialogContent for content that needs more room.
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Field,
FieldContent,
FieldDescription,
FieldLabel,
FieldSeparator,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
export default function DialogWide() {
return (
<Dialog>
<DialogTrigger render={<Button variant="secondary" />}>Project settings</DialogTrigger>
<DialogContent className="sm:max-w-[640px]">
<DialogHeader>
<DialogTitle>Project settings</DialogTitle>
<DialogDescription>
Change this project's name, URL, description and visibility.
</DialogDescription>
</DialogHeader>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<Field>
<FieldLabel htmlFor="project-name">Name</FieldLabel>
<Input id="project-name" defaultValue="Website redesign" />
</Field>
<Field>
<FieldLabel htmlFor="project-slug">URL</FieldLabel>
<Input id="project-slug" defaultValue="website-redesign" />
</Field>
<Field className="sm:col-span-2">
<FieldLabel htmlFor="project-description">Description</FieldLabel>
<Textarea
id="project-description"
defaultValue="Refresh the marketing site with the new brand."
/>
</Field>
</div>
<FieldSeparator />
<Field orientation="horizontal">
<FieldContent>
<FieldLabel htmlFor="project-public">Public project</FieldLabel>
<FieldDescription>Anyone with the link can view tasks and files.</FieldDescription>
</FieldContent>
<Switch id="project-public" />
</Field>
<DialogFooter>
<Button variant="destructive" className="sm:mr-auto">
Delete project
</Button>
<DialogClose render={<Button variant="secondary" />}>Cancel</DialogClose>
<DialogClose render={<Button variant="primary" />}>Save</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
}Footer close button
showCloseButton={false} on DialogContent hides the close button in the corner, and
showCloseButton on DialogFooter adds one to the footer instead.
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Kbd, KbdGroup } from "@/components/ui/kbd";
const shortcuts = [
{ label: "Search", keys: ["⌘", "K"] },
{ label: "New task", keys: ["C"] },
{ label: "Toggle sidebar", keys: ["⌘", "B"] },
];
export default function DialogFooterCloseButton() {
return (
<Dialog>
<DialogTrigger render={<Button variant="ghost" />}>Keyboard shortcuts</DialogTrigger>
<DialogContent showCloseButton={false}>
<DialogHeader>
<DialogTitle>Keyboard shortcuts</DialogTitle>
</DialogHeader>
<div className="flex flex-col gap-2 text-sm">
{shortcuts.map(({ label, keys }) => (
<div key={label} className="flex items-center justify-between">
<span className="text-muted-foreground">{label}</span>
<KbdGroup>
{keys.map((key) => (
<Kbd key={key}>{key}</Kbd>
))}
</KbdGroup>
</div>
))}
</div>
<DialogFooter showCloseButton />
</DialogContent>
</Dialog>
);
}Props
createDialogHandle makes a handle to pass as handle to a Dialog. Its open(null) and
close() open and close the dialog from code, such as a keyboard shortcut, and isOpen says
whether it's open. A DialogTrigger outside the Dialog takes the same handle.
The tables leave out className, style and render. Parts built on Base UI take them as its styling and composition guides describe.
Dialog
| Prop | Type | Default |
|---|---|---|
openWhether the dialog is currently open. | boolean | — |
defaultOpenWhether the dialog is initially open. To render a controlled dialog, use the | boolean | false |
modalDetermines if the dialog enters a modal state when open.
When | "trap-focus" | boolean | true |
onOpenChangeEvent handler called when the dialog is opened or closed. | ((open: boolean, eventDetails: Dialog.Root.ChangeEventDetails) => void) | — |
onOpenChangeCompleteEvent handler called after any animations complete when the dialog is opened or closed. | ((open: boolean) => void) | — |
disablePointerDismissalWhether 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. | boolean | false |
actionsRefA ref to imperative actions.
| React.RefObject<DialogRootActions | null> | — |
handleA 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. | Dialog.Handle<unknown> | — |
childrenThe content of the dialog.
This can be a regular React node or a render function that receives the | PayloadChildRenderFunction<unknown> | React.ReactNode | — |
triggerIdID of the trigger that the dialog is associated with.
This is useful in conjunction with the | string | null | — |
defaultTriggerIdID of the trigger that the dialog is associated with.
This is useful in conjunction with the | string | null | — |
DialogTrigger
| Prop | Type | Default |
|---|---|---|
handleA handle to associate the trigger with a dialog. Can be created with the Dialog.createHandle() method. | Dialog.Handle<unknown> | — |
payloadA payload to pass to the dialog when it is opened. | unknown | — |
idID 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 | string | — |
nativeButtonWhether the component renders a native | boolean | true |
DialogPortal
| Prop | Type | Default |
|---|---|---|
keepMountedWhether to keep the portal mounted in the DOM while the popup is hidden. | boolean | false |
containerA parent element to render the portal element into. | HTMLElement | React.RefObject<HTMLElement | ShadowRoot | null> | ShadowRoot | null | — |
DialogClose
| Prop | Type | Default |
|---|---|---|
nativeButtonWhether the component renders a native | boolean | true |
DialogOverlay
| Prop | Type | Default |
|---|---|---|
forceRenderWhether the backdrop is forced to render even when nested. | boolean | false |
DialogContent
| Prop | Type | Default |
|---|---|---|
initialFocusDetermines 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.
| boolean | React.RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | HTMLElement | null | void) | — |
finalFocusDetermines the element to focus when the dialog is closed.
| boolean | React.RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | HTMLElement | null | void) | — |
showCloseButton | boolean | true |
DialogFooter
| Prop | Type | Default |
|---|---|---|
showCloseButton | boolean | false |