Dialog
Modal dialog for focused tasks.
Preview
Code
import { Button } from "@/components/ui/button";
import {
Dialog, DialogContent, DialogDescription, DialogFooter,
DialogHeader, DialogTitle, DialogTrigger,
} from "@/components/ui/dialog";
export function Example() {
return (
<Dialog>
<DialogTrigger render={<Button variant="outline">Open dialog</Button>} />
<DialogContent>
<DialogHeader>
<DialogTitle>Confirm change</DialogTitle>
<DialogDescription>
This will update the configuration for all operators.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline">Cancel</Button>
<Button>Confirm</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| open | boolean | — | Controlled open state. |
| defaultOpen | boolean | — | Uncontrolled initial open state. |
| onOpenChange | (open: boolean) => void | — | Called when the open state changes. |
Sub-components
DialogTrigger— Element that opens the dialog. Supports `render` for asChild pattern.DialogContent— Modal body, includes backdrop and focus trap.| Prop | Type | Default | Description |
|---|---|---|---|
| showCloseButton | boolean | true | Render the X close button in the top-right. |
DialogHeader— Title + description region.DialogTitle— Required — Base UI uses it for `aria-labelledby`.DialogDescription— Wired to `aria-describedby`.DialogFooter— Actions row at the bottom.DialogClose— Explicit close trigger.Built on Base UI Dialog. Escape closes, backdrop closes, focus returns to trigger. Always include a DialogTitle.
All components also accept standard HTML attributes for their root element (e.g. `id`, `aria-*`, `data-*`, event handlers) and forward `ref` where applicable.
Source
The full implementation of components/ui/dialog.tsx is below — copy it into your project and own it. Some components import the cn helper from lib/utils or other primitives; copy those too.
"use client";
import { cn } from "@/lib/utils";
import { Dialog as BaseDialog } from "@base-ui-components/react/dialog";
import { X } from "lucide-react";
import type { ComponentPropsWithoutRef, ReactNode } from "react";
export const Dialog = BaseDialog.Root;
export const DialogTrigger = BaseDialog.Trigger;
export const DialogClose = BaseDialog.Close;
export function DialogContent({
className,
children,
showCloseButton = true,
...props
}: ComponentPropsWithoutRef<typeof BaseDialog.Popup> & {
showCloseButton?: boolean;
children?: ReactNode;
}) {
return (
<BaseDialog.Portal>
<BaseDialog.Backdrop
className={cn(
"fixed inset-0 z-50 bg-bg/60 backdrop-blur-sm",
"data-[starting-style]:opacity-0 data-[ending-style]:opacity-0",
"transition-opacity duration-200",
)}
/>
<BaseDialog.Popup
className={cn(
"fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4",
"rounded-lg border border-border bg-surface-elevated p-6 shadow-lg",
"data-[starting-style]:opacity-0 data-[starting-style]:scale-95",
"data-[ending-style]:opacity-0 data-[ending-style]:scale-95",
"transition-all duration-200",
className,
)}
{...props}
>
{children}
{showCloseButton && (
<BaseDialog.Close
className={cn(
"absolute right-4 top-4 rounded-sm text-fg-muted opacity-70 transition-opacity",
"hover:opacity-100 focus-visible:outline-1 focus-visible:outline-offset-0 focus-visible:outline-accent",
)}
aria-label="Close"
>
<X className="h-4 w-4" />
</BaseDialog.Close>
)}
</BaseDialog.Popup>
</BaseDialog.Portal>
);
}
export function DialogHeader({ className, ...props }: ComponentPropsWithoutRef<"div">) {
return <div className={cn("flex flex-col gap-1.5 text-left", className)} {...props} />;
}
export function DialogFooter({ className, ...props }: ComponentPropsWithoutRef<"div">) {
return (
<div
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
{...props}
/>
);
}
export function DialogTitle({
className,
...props
}: ComponentPropsWithoutRef<typeof BaseDialog.Title>) {
return (
<BaseDialog.Title
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...props}
/>
);
}
export function DialogDescription({
className,
...props
}: ComponentPropsWithoutRef<typeof BaseDialog.Description>) {
return <BaseDialog.Description className={cn("text-sm text-fg-muted", className)} {...props} />;
}JavaFX
Ships in the PRIZM JavaFX library for thick-client C3 apps as PrizmDialog (extends (controller; overlays a StackPane)). Run the gallery to see it natively.
import design.prizm.fx.controls.PrizmDialog;
PrizmDialog()
PrizmDialog(String title, String message)| Prop | Type | Default | Description |
|---|---|---|---|
| setTitle | (String) → void | — | The in-card title. |
| setBody | (String) → void | — | Body message text. |
| setBody | (Node) → void | — | Body as an arbitrary node (replaces the text). |
| addAction | (String text, PrizmButton.Variant variant, Runnable action) → PrizmButton | — | Add a footer button; the action runs after dismiss (null just closes). |
| show / hide | (StackPane host) → void / () → void | — | Overlay on / remove from a full-size host StackPane. |
An in-scene modal overlay (scrim + centred card), not a native window — so the rounded card renders cleanly with no OS-chrome corner artifacts, and it inherits the scene theme like the other overlays (Sheet / Command). The scrim, a top-right ✕, Esc, and any action button all dismiss it. Needs a full-size host StackPane. Mirrors components/ui/dialog.tsx.