PRIZM 4.0DSTA
All components

Dialog

Modal dialog for focused tasks.

stableBase UI Dialog

Preview

Code

tsx
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

PropTypeDefaultDescription
openbooleanControlled open state.
defaultOpenbooleanUncontrolled initial open state.
onOpenChange(open: boolean) => voidCalled when the open state changes.

Sub-components

DialogTriggerElement that opens the dialog. Supports `render` for asChild pattern.
DialogContentModal body, includes backdrop and focus trap.
PropTypeDefaultDescription
showCloseButtonbooleantrueRender the X close button in the top-right.
DialogHeaderTitle + description region.
DialogTitleRequired — Base UI uses it for `aria-labelledby`.
DialogDescriptionWired to `aria-describedby`.
DialogFooterActions row at the bottom.
DialogCloseExplicit 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.

tsx
"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.

java
import design.prizm.fx.controls.PrizmDialog;

PrizmDialog()
PrizmDialog(String title, String message)
PropTypeDefaultDescription
setTitle(String) → voidThe in-card title.
setBody(String) → voidBody message text.
setBody(Node) → voidBody as an arbitrary node (replaces the text).
addAction(String text, PrizmButton.Variant variant, Runnable action) → PrizmButtonAdd a footer button; the action runs after dismiss (null just closes).
show / hide(StackPane host) → void / () → voidOverlay 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.