Overview
Get Started
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogBody,
DialogClose,
DialogDescription,
DialogFooter,
DialogHeader,
DialogPopup,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Field, FieldLabel } from "@/components/ui/field";
import { Form } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
export function DialogDemo() {
return (
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>Open Dialog</DialogTrigger>
<DialogPopup>
<Form className="grid">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're done.
</DialogDescription>
</DialogHeader>
<DialogBody className="grid gap-4">
<Field>
<FieldLabel>Name</FieldLabel>
<Input defaultValue="ZBM Zubayer" type="text" />
</Field>
<Field>
<FieldLabel>Username</FieldLabel>
<Input defaultValue="zbmzubayer@example.com" type="text" />
</Field>
</DialogBody>
<DialogFooter>
<DialogClose render={<Button variant="ghost" />}>Cancel</DialogClose>
<Button type="submit">Save</Button>
</DialogFooter>
</Form>
</DialogPopup>
</Dialog>
);
}Installation
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
import { IconX } from "@tabler/icons-react";
import { cn } from "@/lib/cn";
import { buttonVariants } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
export const dialogCreateHandle = DialogPrimitive.createHandle;
export const Dialog = DialogPrimitive.Root;
export function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
}
export function DialogTrigger(props: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
}
export function DialogClose(props: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
}
type DialogBackdropVariant = "blur" | "opaque" | "transparent";
export function DialogBackdrop({
className,
variant = "blur",
...props
}: React.ComponentProps<typeof DialogPrimitive.Backdrop> & {
variant?: DialogBackdropVariant;
}) {
return (
<DialogPrimitive.Backdrop
data-slot="dialog-backdrop"
className={cn(
"fixed inset-0 z-50 transition-opacity duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0",
variant === "blur" && "bg-black/30 backdrop-blur-sm",
variant === "opaque" && "bg-black/50",
variant === "transparent" && "bg-transparent",
className
)}
{...props}
/>
);
}
type DialogPopupPosition =
| "top"
| "center"
| "bottom"
| "top-left"
| "top-right"
| "bottom-left"
| "bottom-right"
| "top-center"
| "bottom-center";
function DialogViewport({
className,
position = "center",
...props
}: React.ComponentProps<typeof DialogPrimitive.Viewport> & {
position?: DialogPopupPosition;
}) {
return (
<DialogPrimitive.Viewport
data-slot="dialog-viewport"
className={cn(
"fixed inset-0 z-50 flex flex-col p-4",
position === "top" && "items-center justify-start",
position === "center" && "items-center justify-center",
position === "bottom" && "items-center justify-end",
position === "top-left" && "items-start justify-start",
position === "top-right" && "items-end justify-start",
position === "bottom-left" && "items-start justify-end",
position === "bottom-right" && "items-end justify-end",
position === "top-center" &&
"grid grid-rows-[1fr_auto_4fr] justify-items-center *:data-[slot=dialog-popup]:row-start-2",
position === "bottom-center" &&
"grid grid-rows-[4fr_auto_1fr] justify-items-center *:data-[slot=dialog-popup]:row-start-2",
className
)}
{...props}
/>
);
}
export function DialogPopup({
className,
children,
bottomStickyOnMobile = false,
showCloseButton = true,
showNestedParent = false,
backdropVariant,
position,
closeProps: { className: closeClassName, ...closeProps } = {},
portalProps,
...props
}: React.ComponentProps<typeof DialogPrimitive.Popup> & {
bottomStickyOnMobile?: boolean;
showCloseButton?: boolean;
showNestedParent?: boolean;
backdropVariant?: DialogBackdropVariant;
position?: DialogPopupPosition;
closeProps?: React.ComponentProps<typeof DialogPrimitive.Close>;
portalProps?: React.ComponentProps<typeof DialogPrimitive.Portal>;
}) {
return (
<DialogPortal {...portalProps}>
<DialogBackdrop variant={backdropVariant} />
<DialogViewport
position={position}
className={cn(
bottomStickyOnMobile && "max-sm:items-center max-sm:justify-end max-sm:p-0 max-sm:pt-12"
)}
>
<DialogPrimitive.Popup
data-slot="dialog-popup"
className={cn(
"relative flex max-h-full w-full max-w-lg origin-center scale-[calc(1-0.1*var(--nested-dialogs))] flex-col overflow-hidden rounded-2xl border bg-popover text-popover-foreground shadow-lg transition-[scale,opacity,translate] duration-200 ease-in-out will-change-transform data-nested:data-ending-style:translate-y-8 data-nested:data-starting-style:translate-y-8 data-nested-dialog-open:origin-top data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0",
showNestedParent
? "-translate-y-[calc(2rem*var(--nested-dialogs))] opacity-[calc(1-0.1*var(--nested-dialogs))]"
: "opacity-[calc(1-var(--nested-dialogs))]",
bottomStickyOnMobile &&
"max-sm:max-w-none max-sm:origin-bottom max-sm:rounded-b-none max-sm:border-x-0 max-sm:border-t max-sm:border-b-0 max-sm:data-ending-style:translate-y-4 max-sm:data-starting-style:translate-y-4",
className
)}
{...props}
>
{children}
{showCloseButton && (
<DialogPrimitive.Close
aria-label="Close"
data-slot="dialog-close"
className={cn(
buttonVariants({ size: "icon", variant: "ghost" }),
"absolute end-2 top-2",
closeClassName
)}
{...closeProps}
>
<IconX />
</DialogPrimitive.Close>
)}
</DialogPrimitive.Popup>
</DialogViewport>
</DialogPortal>
);
}
export function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn(
"flex flex-col gap-2 p-6 in-[[data-slot=dialog-popup]:has([data-slot=dialog-panel])]:pb-2 max-sm:px-4",
className
)}
{...props}
/>
);
}
export function DialogTitle({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn("font-heading font-medium text-xl leading-none", className)}
{...props}
/>
);
}
export function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn("text-pretty text-muted-foreground text-sm", className)}
{...props}
/>
);
}
export function DialogFooter({
className,
variant = "default",
...props
}: React.ComponentProps<"div"> & { variant?: "default" | "muted" }) {
return (
<div
data-slot="dialog-footer"
className={cn(
"flex flex-col-reverse gap-2 p-4 sm:flex-row sm:justify-end sm:px-6",
variant === "default" && "pt-2 pb-6",
variant === "muted" && "border-t bg-muted/50",
className
)}
{...props}
/>
);
}
export function DialogBody({
className,
scrollArea = true,
scrollFade = true,
scrollbarGutter,
...props
}: React.ComponentProps<"div"> & {
scrollArea?: boolean;
scrollFade?: React.ComponentProps<typeof ScrollArea>["scrollFade"];
scrollbarGutter?: React.ComponentProps<typeof ScrollArea>["scrollbarGutter"];
}) {
return scrollArea ? (
<ScrollArea scrollFade={scrollFade} scrollbarGutter={scrollbarGutter}>
<div
data-slot="dialog-body"
className={cn(
"p-6 in-[[data-slot=dialog-popup]:has([data-slot=dialog-header])]:pt-1 max-sm:px-4",
className
)}
{...props}
/>
</ScrollArea>
) : (
<div data-slot="dialog-body" className={cn("overflow-auto p-6", className)} {...props} />
);
}
export { DialogPrimitive };Usage
import {
Dialog,
DialogBody,
DialogClose,
DialogDescription,
DialogFooter,
DialogHeader,
DialogPopup,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";<Dialog>
<DialogTrigger>Open Dialog</DialogTrigger>
<DialogPopup>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogDescription>Dialog description text goes here.</DialogDescription>
</DialogHeader>
<DialogBody>
Dialog content goes here. You can put forms, text, or any other elements inside the dialog.
</DialogBody>
<DialogFooter>
<DialogClose>Close</DialogClose>
</DialogFooter>
</DialogPopup>
</Dialog>Examples
With muted footer
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogBody,
DialogClose,
DialogDescription,
DialogFooter,
DialogHeader,
DialogPopup,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Field, FieldLabel } from "@/components/ui/field";
import { Form } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
export function DialogDemo() {
return (
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>Open Dialog</DialogTrigger>
<DialogPopup>
<Form className="contents">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're done.
</DialogDescription>
</DialogHeader>
<DialogBody className="grid gap-4">
<Field>
<FieldLabel>Name</FieldLabel>
<Input defaultValue="ZBM Zubayer" type="text" />
</Field>
<Field>
<FieldLabel>Username</FieldLabel>
<Input defaultValue="zbmzubayer@example.com" type="text" />
</Field>
</DialogBody>
<DialogFooter variant="muted">
<DialogClose render={<Button variant="ghost" />}>Cancel</DialogClose>
<Button type="submit">Save</Button>
</DialogFooter>
</Form>
</DialogPopup>
</Dialog>
);
}Backdrop variants
The backdrop of the dialog can be customized using the backdropVariant prop on DialogPopup. The available variants are blur (default), opaque, and transparent.
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogDescription,
DialogHeader,
DialogPopup,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
const backdropVariants: React.ComponentProps<typeof DialogPopup>["backdropVariant"][] = [
"blur",
"opaque",
"transparent",
];
export function DialogDemo() {
return (
<div className="flex flex-wrap items-center gap-5">
{backdropVariants.map((variant) => (
<Dialog key={variant}>
<DialogTrigger render={<Button variant="outline" className="capitalize" />}>
{variant}
</DialogTrigger>
<DialogPopup backdropVariant={variant}>
<DialogHeader>
<DialogTitle>Title</DialogTitle>
<DialogDescription>Description</DialogDescription>
</DialogHeader>
</DialogPopup>
</Dialog>
))}
</div>
);
}Popup position
Change the position of the popup by passing a position prop to DialogPopup. The default position is center. Other options include top, bottom, top-center, and bottom-center.
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogDescription,
DialogHeader,
DialogPopup,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
export const positions: React.ComponentProps<typeof DialogPopup>["position"][] = [
"top-left",
"top",
"top-right",
"top-center",
"center",
"bottom-center",
"bottom-left",
"bottom",
"bottom-right",
];
export function DialogDemo() {
return (
<div className="grid grid-cols-2 items-center gap-5 sm:grid-cols-3">
{positions.map((position) => (
<Dialog key={position}>
<DialogTrigger render={<Button variant="outline" className="capitalize" />}>
{position}
</DialogTrigger>
<DialogPopup position={position}>
<DialogHeader>
<DialogTitle>Title</DialogTitle>
<DialogDescription>Description</DialogDescription>
</DialogHeader>
</DialogPopup>
</Dialog>
))}
</div>
);
}No close button
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogDescription,
DialogHeader,
DialogPopup,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
export function DialogDemo() {
return (
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>Open Dialog</DialogTrigger>
<DialogPopup showCloseButton={false}>
<DialogHeader>
<DialogTitle>Title</DialogTitle>
<DialogDescription>
Description text goes here. You can provide additional information about the dialog
content or instructions for the user.
</DialogDescription>
</DialogHeader>
</DialogPopup>
</Dialog>
);
}Scrollable content
Scroll Area (Default)
Native scrollbar
No scrollbar
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogBody,
DialogClose,
DialogFooter,
DialogHeader,
DialogPopup,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
export function DialogDemo() {
return (
<div className="flex flex-col gap-5">
<Dialog>
<div className="grid grid-cols-2 items-center gap-2 text-sm">
<p>Scroll Area (Default)</p>
<DialogTrigger render={<Button variant="outline" />}>Terms & Conditions</DialogTrigger>
</div>
<DialogPopup className="sm:max-w-md" showCloseButton={false}>
<DialogHeader>
<DialogTitle>Terms & Conditions</DialogTitle>
</DialogHeader>
<DialogBody>
<ScrollContent />
</DialogBody>
<DialogFooter>
<DialogClose render={<Button variant="ghost" />}>Cancel</DialogClose>
<Button type="button">I agree</Button>
</DialogFooter>
</DialogPopup>
</Dialog>
<Dialog>
<div className="grid grid-cols-2 items-center gap-2 text-sm">
<p>Native scrollbar</p>
<DialogTrigger render={<Button variant="outline" />}>Terms & Conditions</DialogTrigger>
</div>
<DialogPopup className="sm:max-w-md" showCloseButton={false}>
<DialogHeader>
<DialogTitle>Terms & Conditions</DialogTitle>
</DialogHeader>
<DialogBody scrollArea={false} className="border-y py-4 [scrollbar-width:thin]">
<ScrollContent />
</DialogBody>
<DialogFooter>
<DialogClose render={<Button variant="ghost" />}>Cancel</DialogClose>
<Button type="button">I agree</Button>
</DialogFooter>
</DialogPopup>
</Dialog>
<Dialog>
<div className="grid grid-cols-2 items-center gap-2 text-sm">
<p>No scrollbar</p>
<DialogTrigger render={<Button variant="outline" />}>Terms & Conditions</DialogTrigger>
</div>
<DialogPopup className="sm:max-w-md" showCloseButton={false}>
<DialogHeader>
<DialogTitle>Terms & Conditions</DialogTitle>
</DialogHeader>
<DialogBody scrollArea={false} className="no-scrollbar">
<ScrollContent />
</DialogBody>
<DialogFooter>
<DialogClose render={<Button variant="ghost" />}>Cancel</DialogClose>
<Button type="button">I agree</Button>
</DialogFooter>
</DialogPopup>
</Dialog>
</div>
);
}
function ScrollContent() {
return (
<div className="space-y-4 [&_strong]:font-semibold [&_strong]:text-foreground">
<div className="space-y-4">
<div className="space-y-1">
<p>
<strong>Acceptance of Terms</strong>
</p>
<p>
By accessing and using this website, users agree to comply with and be bound by these
Terms of Service. Users who do not agree with these terms should discontinue use of the
website immediately.
</p>
</div>
<div className="space-y-1">
<p>
<strong>User Account Responsibilities</strong>
</p>
<p>
Users are responsible for maintaining the confidentiality of their account credentials.
Any activities occurring under a user's account are the sole responsibility of the
account holder. Users must notify the website administrators immediately of any
unauthorized account access.
</p>
</div>
<div className="space-y-1">
<p>
<strong>Content Usage and Restrictions</strong>
</p>
<p>
The website and its original content are protected by intellectual property laws. Users
may not reproduce, distribute, modify, create derivative works, or commercially exploit
any content without explicit written permission from the website owners.
</p>
</div>
<div className="space-y-1">
<p>
<strong>Limitation of Liability</strong>
</p>
<p>
The website provides content “as is” without any warranties. The website
owners shall not be liable for direct, indirect, incidental, consequential, or punitive
damages arising from user interactions with the platform.
</p>
</div>
<div className="space-y-1">
<p>
<strong>User Conduct Guidelines</strong>
</p>
<ul className="list-disc pl-6">
<li>Not upload harmful or malicious content</li>
<li>Respect the rights of other users</li>
<li>Avoid activities that could disrupt website functionality</li>
<li>Comply with applicable local and international laws</li>
</ul>
</div>
<div className="space-y-1">
<p>
<strong>Modifications to Terms</strong>
</p>
<p>
The website reserves the right to modify these terms at any time. Continued use of the
website after changes constitutes acceptance of the new terms.
</p>
</div>
<div className="space-y-1">
<p>
<strong>Termination Clause</strong>
</p>
<p>
The website may terminate or suspend user access without prior notice for violations of
these terms or for any other reason deemed appropriate by the administration.
</p>
</div>
<div className="space-y-1">
<p>
<strong>Governing Law</strong>
</p>
<p>
These terms are governed by the laws of the jurisdiction where the website is primarily
operated, without regard to conflict of law principles.
</p>
</div>
</div>
</div>
);
}Nested dialogs
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogBody,
DialogClose,
DialogDescription,
DialogFooter,
DialogHeader,
DialogPopup,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function DialogDemo() {
const [parentOpen, setParentOpen] = useState(false);
return (
<Dialog open={parentOpen} onOpenChange={setParentOpen}>
<DialogTrigger render={<Button variant="outline" />}>Open parent</DialogTrigger>
<DialogPopup showCloseButton={false}>
<DialogHeader>
<DialogTitle>Manage team member</DialogTitle>
<DialogDescription>View and manage a user in your team.</DialogDescription>
</DialogHeader>
<DialogBody className="grid gap-4">
<div className="grid gap-1">
<p className="text-muted-foreground text-sm">Name</p>
<p className="font-medium text-sm">ZBM Zubayer</p>
</div>
<div className="grid gap-1">
<p className="text-muted-foreground text-sm">Email</p>
<p className="font-medium text-sm">zbm@example.com</p>
</div>
</DialogBody>
<DialogFooter>
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>Edit details</DialogTrigger>
<DialogPopup showCloseButton={false}>
<DialogHeader>
<DialogTitle>Edit details</DialogTitle>
<DialogDescription>
Make changes to the member's information.
</DialogDescription>
</DialogHeader>
<DialogBody className="grid gap-4">
<Field>
<FieldLabel>Name</FieldLabel>
<Input defaultValue="ZBM Zubayer" type="text" />
</Field>
<Field>
<FieldLabel>Email</FieldLabel>
<Input defaultValue="zbm@example.com" type="text" />
</Field>
</DialogBody>
<DialogFooter>
<DialogClose render={<Button variant="ghost" />}>Cancel</DialogClose>
<DialogClose render={<Button onClick={() => setParentOpen(false)} />}>
Save
</DialogClose>
</DialogFooter>
</DialogPopup>
</Dialog>
</DialogFooter>
</DialogPopup>
</Dialog>
);
}Show parent dialog of nested dialog
To show the parent dialog when a nested dialog is open, pass the showNestedParent prop to DialogPopup. This will keep the parent dialog visible while the nested dialog is open.
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogBody,
DialogClose,
DialogDescription,
DialogFooter,
DialogHeader,
DialogPopup,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Field, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
export function DialogDemo() {
const [parentOpen, setParentOpen] = useState(false);
return (
<Dialog open={parentOpen} onOpenChange={setParentOpen}>
<DialogTrigger render={<Button variant="outline" />}>Open parent</DialogTrigger>
<DialogPopup showCloseButton={false} showNestedParent>
<DialogHeader>
<DialogTitle>Manage team member</DialogTitle>
<DialogDescription>View and manage a user in your team.</DialogDescription>
</DialogHeader>
<DialogBody className="grid gap-4">
<div className="grid gap-1">
<p className="text-muted-foreground text-sm">Name</p>
<p className="font-medium text-sm">ZBM Zubayer</p>
</div>
<div className="grid gap-1">
<p className="text-muted-foreground text-sm">Email</p>
<p className="font-medium text-sm">zbm@example.com</p>
</div>
</DialogBody>
<DialogFooter>
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>Edit details</DialogTrigger>
<DialogPopup showCloseButton={false}>
<DialogHeader>
<DialogTitle>Edit details</DialogTitle>
<DialogDescription>
Make changes to the member's information.
</DialogDescription>
</DialogHeader>
<DialogBody className="grid gap-4">
<Field>
<FieldLabel>Name</FieldLabel>
<Input defaultValue="ZBM Zubayer" type="text" />
</Field>
<Field>
<FieldLabel>Email</FieldLabel>
<Input defaultValue="zbm@example.com" type="text" />
</Field>
</DialogBody>
<DialogFooter>
<DialogClose render={<Button variant="ghost" />}>Cancel</DialogClose>
<DialogClose render={<Button onClick={() => setParentOpen(false)} />}>
Save
</DialogClose>
</DialogFooter>
</DialogPopup>
</Dialog>
</DialogFooter>
</DialogPopup>
</Dialog>
);
}Api Reference
Dialog Backdrop props
Component type props
Prop
Type
Default
Expand prop details
The variant of the backdrop.
Type
'blur' | 'opaque' | 'transparent'Default
'blur'Dialog Viewport props
Component type props
Prop
Type
Default
Expand prop details
The position of the popup.
Type
'center' | 'top' | 'bottom' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'top-center' | 'bottom-center'Default
'center'Dialog Popup props
Component type props
Prop
Type
Default
Expand prop details
Whether to make the popup bottom sticky on mobile devices. This is useful for dialogs that have important actions in the footer that should be easily accessible on mobile.
Type
booleanDefault
falseWhether to show the close button in the header.
Type
booleanDefault
trueWhether to show the parent dialog when a nested dialog is open. Useful for keeping the context of the parent dialog visible while interacting with the nested dialog.
Type
booleanDefault
falseThe variant of the backdrop.
Type
'blur' | 'opaque' | 'transparent'Default
'blur'The position of the popup.
Type
'center' | 'top' | 'bottom' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'top-center' | 'bottom-center'Default
'center'Dialog Body props
Component type props
Prop
Type
Default
Expand prop details
Whether to wrap the body in a scroll area. If false, native scrollbar will be shown if the content overflows.
Type
booleanDefault
trueWhether to show a fade effect at the top and bottom of the scroll area.
Type
booleanDefault
trueWhether to show the scrollbar gutter when `scrollArea` is true.
Type
booleanDefault
falseDialog Footer props
Component type props
Prop
Type
Default
Expand prop details
The variant of the footer.
Type
'default' | 'muted'Default
'default'