Dialog
A modal dialog overlay with customizable header, content, and footer.
Installation
$
Usage
import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose } from "@/components/ui/dialog"Examples
Default
A form dialog. DialogFooter showCancelButton adds a Cancel that closes the dialog, placed left of your primary action. initialFocus focuses the first field with the caret at the end instead of selecting its text.
Scrollable
Long content scrolls in a bounded region (custom scrollbar) while the header and footer stay fixed. gap-0 on the content keeps both dividers on a symmetric 16px (py-4 above, pb-4 / pt-4 below).
Share
A utility dialog: read-only field with an in-field copy button (Input actionEnd) and a Done action. initialFocus moves focus to the copy button instead of auto-selecting the URL.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| openDialog | boolean | - | Controlled open state. Pair with onOpenChange. |
| onOpenChangeDialog | (open: boolean) => void | - | Called when the open state changes. |
| defaultOpenDialog | boolean | false | Uncontrolled initial open state. |
| modalDialog | boolean | true | When true, blocks interaction with the rest of the page and traps focus. |
| showCloseButtonDialogContent | boolean | true | Shows the X close button in the top-right corner. |
| showCancelButtonDialogFooter | boolean | false | Adds a ghost Cancel button that closes the dialog, placed left of your actions. |
| initialFocusDialogContent | boolean | React.RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | HTMLElement | null | void) | true | The element to focus when the dialog opens. Pass a ref to focus a specific element, or a function returning an element, true for default behavior, or false/null/void to leave focus alone. |
| renderDialogTrigger, DialogClose | React.ReactElement | - | Replaces the default trigger or close button with your own element, e.g. render={<Button>Done</Button>}. Base UI merges props onto it. |
"use client"
import * as React from "react"
import { XIcon } from "lucide-react"
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
import { cn } from "@/registry/lib/utils"
import { Button } from "@/registry/ui/button"
import { blurredOverlayClass } from "@/registry/lib/tokens"
function Dialog({
...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
function DialogTrigger({
...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
function DialogPortal({
...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
function DialogClose({
...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
function DialogOverlay({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Backdrop>) {
return (
<DialogPrimitive.Backdrop
data-slot="dialog-overlay"
className={cn(
blurredOverlayClass,
className
)}
{...props}
/>
)
}
function DialogContent({
className,
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Popup> & {
showCloseButton?: boolean
}) {
return (
<DialogPortal data-slot="dialog-portal">
<DialogOverlay />
<DialogPrimitive.Popup
data-slot="dialog-content"
className={cn(
"bg-popover transition-[opacity,scale] motion-reduce:transition-[opacity] ease-out duration-200 data-[ending-style]:duration-150 data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:scale-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] max-h-[calc(100dvh-2rem)] overflow-y-auto translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg p-6 shadow-ring-lg outline-none sm:max-w-lg",
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
data-slot="dialog-close"
className="tap-target ring-offset-background focus-visible:ring-ring-focus data-[open]:bg-accent data-[open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity ease-out hover:opacity-100 focus-visible:ring-[3px] focus-visible:ring-offset-2 outline-none disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
>
<XIcon />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Popup>
</DialogPortal>
)
}
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
{...props}
/>
)
}
function DialogFooter({
className,
showCancelButton = false,
children,
...props
}: React.ComponentProps<"div"> & {
showCancelButton?: boolean
}) {
return (
<div
data-slot="dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className
)}
{...props}
>
{showCancelButton && (
<DialogPrimitive.Close render={<Button variant="ghost">Cancel</Button>} />
)}
{children}
</div>
)
}
function DialogTitle({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn("text-lg leading-none font-semibold", className)}
{...props}
/>
)
}
function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
}