Dialog

Pre-styled compound dialog built on Base UI. Handles backdrop, portal, animations, and accessible title.

API Reference

DialogRoot

PropType
openboolean
onOpenChange(open: boolean) => void

DialogBackdrop

PropType
classNamestring

DialogContent

The popup panel. Renders inside a portal.

PropType
classNamestring

DialogTitle

Accessible title. Defaults to sr-only.

PropTypeDefault
classNamestring"sr-only"

Basic Usage

import { DialogRoot, DialogBackdrop, DialogContent, DialogTitle } from "@/ui";

<DialogRoot open={open} onOpenChange={setOpen}>
  <DialogBackdrop />
  <DialogContent>
    <DialogTitle>My Dialog</DialogTitle>
    {/* content */}
  </DialogContent>
</DialogRoot>

Usage Notes

  • This is a pre-styled wrapper around Base UI Dialog. Refer to their docs for the full API and accessibility details.
  • DialogRoot bundles both the Base UI Root and Portal — no need to add a Portal yourself.
  • DialogTitle renders as sr-only by default. Pass a className to make it visible.
  • Override any default styles by passing className to any sub-component.

Source

src/ui/Dialog.tsx
"use client";

import { Dialog as BaseDialog } from "@base-ui/react/dialog";
import { cn } from "@/utils";

interface DialogRootProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  children: React.ReactNode;
}

function DialogRoot({ open, onOpenChange, children }: DialogRootProps) {
  return (
    <BaseDialogRoot open={open} onOpenChange={onOpenChange}>
      <BaseDialog.Portal>{children}</BaseDialog.Portal>
    </BaseDialogRoot>
  );
}

function DialogBackdrop({ className }: { className?: string }) {
  return (
    <BaseDialogBackdrop
      className={cn(
        "fixed inset-0 z-50 bg-overlay transition-opacity duration-200 data-exiting:opacity-0 data-open:opacity-100 opacity-0",
        className,
      )}
    />
  );
}

interface DialogContentProps {
  className?: string;
  children: React.ReactNode;
}

function DialogContent({ className, children }: DialogContentProps) {
  return (
    <BaseDialog.Popup
      className={cn(
        "fixed inset-2 z-50 overflow-hidden rounded-xl bg-root transition-[opacity,transform] duration-200 data-exiting:opacity-0 data-exiting:scale-95 data-open:opacity-100 data-open:scale-100 opacity-0 scale-95 sm:inset-4",
        className,
      )}
    >
      {children}
    </BaseDialog.Popup>
  );
}

interface DialogTitleProps {
  className?: string;
  children: React.ReactNode;
}

function DialogTitle({ className = "sr-only", children }: DialogTitleProps) {
  return (
    <BaseDialogTitle className={className}>{children}</BaseDialogTitle>
  );
}

export { DialogRoot, DialogBackdrop, DialogContent, DialogTitle };