import { Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@registry/components/ui/dialog";
import { Button } from "@registry/components/ui/button";

export function DialogExample() {
  return (
    <Dialog>
      <DialogTrigger render={<Button variant="outline">Open Dialog</Button>} />
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Confirm Action</DialogTitle>
          <DialogDescription>
            Are you sure you want to proceed? This action cannot be undone.
          </DialogDescription>
        </DialogHeader>
        <DialogBody>
          <p className="text-sm text-gray-600 dark:text-gray-400">
            Additional context or details about the action can go here.
          </p>
        </DialogBody>
        <DialogFooter>
          <DialogClose render={<Button variant="secondary">Cancel</Button>} />
          <Button>Continue</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

Examples

Without close button

Set closeButton={false} on DialogHeader to hide the default close button.

import { Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@registry/components/ui/dialog";
import { Button } from "@registry/components/ui/button";

export function DialogNoCloseButtonExample() {
  return (
    <Dialog>
      <DialogTrigger render={<Button variant="outline">No Close Button</Button>} />
      <DialogContent>
        <DialogHeader closeButton={false}>
          <DialogTitle>Notice</DialogTitle>
          <DialogDescription>
            This dialog has no close button in the header. Use the action below to dismiss it.
          </DialogDescription>
        </DialogHeader>
        <DialogBody>
          <p className="text-sm text-gray-600 dark:text-gray-400">
            You must explicitly choose an action to close this dialog.
          </p>
        </DialogBody>
        <DialogFooter>
          <DialogClose render={<Button>Got it</Button>} />
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

Use direction="row" on DialogFooter to align buttons horizontally.

import { Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@registry/components/ui/dialog";
import { Button } from "@registry/components/ui/button";

export function DialogRowFooterExample() {
  return (
    <Dialog>
      <DialogTrigger render={<Button variant="outline">Row Footer</Button>} />
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Save Changes</DialogTitle>
          <DialogDescription>Your changes will be saved to the cloud.</DialogDescription>
        </DialogHeader>
        <DialogBody>
          <p className="text-sm text-gray-600 dark:text-gray-400">
            Review your changes before confirming.
          </p>
        </DialogBody>
        <DialogFooter direction="row">
          <DialogClose render={<Button variant="secondary">Cancel</Button>} />
          <Button>Save</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

Installation

Copy the source code below into your project:

import { mergeProps, useRender } from "@base-ui/react";
import { Dialog as BaseDialog } from "@base-ui/react/dialog";
import { cn } from "@registry/lib/utils";
import { XIcon } from "@phosphor-icons/react";

import { Button } from "./button";

const Dialog = (props: BaseDialog.Root.Props) => <BaseDialog.Root data-slot="dialog" {...props} />;
const DialogPortal = (props: BaseDialog.Portal.Props) => (
  <BaseDialog.Portal data-slot="dialog-portal" {...props} />
);
const DialogClose = (props: BaseDialog.Close.Props) => (
  <BaseDialog.Close data-slot="dialog-close" {...props} />
);

function DialogTrigger(props: BaseDialog.Trigger.Props) {
  return <BaseDialog.Trigger data-slot="dialog-trigger" {...props} />;
}

const createDialogHandle = BaseDialog.createHandle;

function DialogBackdrop({ className, ...props }: BaseDialog.Backdrop.Props) {
  return (
    <BaseDialog.Backdrop
      className={cn(
        "fixed inset-0 bg-black/30 backdrop-blur-md transition-all duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0",
        className,
      )}
      data-slot="dialog-backdrop"
      {...props}
    />
  );
}

function DialogTitle(props: BaseDialog.Title.Props) {
  return <BaseDialog.Title className="text-lg font-bold" data-slot="dialog-title" {...props} />;
}

function DialogDescription({ className, ...props }: BaseDialog.Description.Props) {
  return (
    <BaseDialog.Description
      className={cn("text-sm text-muted-foreground", className)}
      data-slot="dialog-description"
      {...props}
    />
  );
}

function DialogContent({
  className,
  children,
  dialogPortalProps,
  dialogBackdropProps,
  ...props
}: BaseDialog.Popup.Props & {
  dialogPortalProps?: BaseDialog.Portal.Props;
  dialogBackdropProps?: BaseDialog.Backdrop.Props;
}) {
  return (
    <BaseDialog.Portal {...dialogPortalProps}>
      <DialogBackdrop {...dialogBackdropProps} />
      <BaseDialog.Popup
        className={cn(
          "fixed top-1/2 left-1/2 -mt-8 flex w-96 max-w-[calc(100vw-3rem)] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-3xl bg-dialog text-dialog-foreground shadow-xs transition-all duration-150 data-ending-style:scale-90 data-ending-style:opacity-0 data-starting-style:scale-90 data-starting-style:opacity-0",
          className,
        )}
        data-slot="dialog-popup"
        {...props}
      >
        {children}
      </BaseDialog.Popup>
    </BaseDialog.Portal>
  );
}

function DialogHeader({
  className,
  children,
  closeButton = true,
}: {
  className?: string;
  closeButton?: boolean;
  children?: React.ReactNode;
}) {
  return (
    <div className={cn("relative flex flex-col p-6", className)} data-slot="dialog-header">
      {closeButton && (
        <DialogClose
          className="absolute top-4 right-4"
          render={<Button className="size-5" size="icon" variant="secondary" />}
        >
          <XIcon className="size-3" />
        </DialogClose>
      )}

      {children}
    </div>
  );
}

function DialogBody({ className, ...props }: useRender.ComponentProps<"div">) {
  const dialogBodyElement = useRender({
    defaultTagName: "div",
    props: {
      ...mergeProps<"div">(props, {
        className: cn("flex flex-col gap-3 px-6 pb-6", className),
      }),
      "data-slot": "dialog-body",
    },
  });
  return dialogBodyElement;
}

function DialogFooter({
  children,
  direction = "column",
  className,
}: {
  children: React.ReactNode;
  direction?: "row" | "column";
  className?: string;
}) {
  return (
    <div
      className={cn(
        "flex gap-2 border-t border-border bg-muted p-6",
        direction === "row" && "flex-row justify-end",
        direction === "column" && "flex-col",
        className,
      )}
      data-slot="dialog-footer"
    >
      {children}
    </div>
  );
}

export {
  Dialog,
  DialogBackdrop,
  DialogTrigger,
  DialogPortal,
  DialogTitle,
  DialogDescription,
  DialogClose,
  DialogContent,
  DialogFooter,
  DialogBody,
  DialogHeader,
  createDialogHandle,
};

API Reference

Dialog

This component does not add any props on top of Base UI Dialog.Root . See the Base UI docs for the full API reference.

DialogTrigger

This component does not add any props on top of Base UI Dialog.Trigger . See the Base UI docs for the full API reference.

DialogContent

The DialogContent component extends the Base UI Dialog.Popup props and adds the following:

PropTypeDefaultDescription
dialogPortalPropsBaseDialog.Portal.PropsProps forwarded to the underlying Portal component.
dialogBackdropPropsBaseDialog.Backdrop.PropsProps forwarded to the underlying Backdrop component.

DialogHeader

PropTypeDefaultDescription
closeButtonbooleantrueWhether to show the default close button in the header.
classNamestringAdditional CSS classes to apply to the header container.

DialogBody

PropTypeDefaultDescription
classNamestringAdditional CSS classes to apply to the body container.

DialogFooter

PropTypeDefaultDescription
direction"row" | "column""column"Layout direction for the footer actions.
classNamestringAdditional CSS classes to apply to the footer container.

DialogTitle

This component does not add any props on top of Base UI Dialog.Title . See the Base UI docs for the full API reference.

DialogDescription

The DialogDescription component extends the Base UI Dialog.Description props and adds the following:

PropTypeDefaultDescription
classNamestringAdditional CSS classes to apply to the description element.

DialogBackdrop

The DialogBackdrop component extends the Base UI Dialog.Backdrop props and adds the following:

PropTypeDefaultDescription
classNamestringAdditional CSS classes to apply to the backdrop element.

DialogClose

This component does not add any props on top of Base UI Dialog.Close . See the Base UI docs for the full API reference.

DialogPortal

This component does not add any props on top of Base UI Dialog.Portal . See the Base UI docs for the full API reference.