Dialog
Pre-styled compound dialog built on Base UI. Handles backdrop, portal, animations, and accessible title.
DialogRoot
| Prop | Type |
|---|---|
| open | boolean |
| onOpenChange | (open: boolean) => void |
DialogBackdrop
| Prop | Type |
|---|---|
| className | string |
DialogContent
The popup panel. Renders inside a portal.
| Prop | Type |
|---|---|
| className | string |
DialogTitle
Accessible title. Defaults to sr-only.
| Prop | Type | Default |
|---|---|---|
| className | string | "sr-only" |
import { DialogRoot, DialogBackdrop, DialogContent, DialogTitle } from "@/ui";
<DialogRoot open={open} onOpenChange={setOpen}>
<DialogBackdrop />
<DialogContent>
<DialogTitle>My Dialog</DialogTitle>
{/* content */}
</DialogContent>
</DialogRoot>- This is a pre-styled wrapper around Base UI Dialog. Refer to their docs for the full API and accessibility details.
DialogRootbundles both the Base UI Root and Portal — no need to add a Portal yourself.DialogTitlerenders assr-onlyby default. Pass aclassNameto make it visible.- Override any default styles by passing
classNameto any sub-component.
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 };