- Docs
- Components
- Dialog
Dialog
A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.
Preview
Code
Installation
CLI
Manual
Copy and paste the following code into your project.
"use client";
import {
Button,
Dialog as DialogPrimitive,
DialogTrigger,
Heading,
Modal,
ModalOverlay,
} from "react-aria-components";
import type { VariantProps } from "~/lib/cva";
import { DialogStyles } from "./styles";
export interface DialogRootProps
extends React.ComponentProps<typeof DialogTrigger> {}
export const DialogRoot = DialogTrigger;
export interface DialogOverlayProps
extends React.ComponentProps<typeof ModalOverlay>,
VariantProps<(typeof DialogStyles)["Overlay"]> {}
export function DialogOverlay({
className,
isBlurred,
isDismissable = true,
...props
}: DialogOverlayProps) {
return (
<ModalOverlay
{...props}
isDismissable={isDismissable}
className={DialogStyles.Overlay({ className, isBlurred })}
/>
);
}
export interface DialogModalProps
extends React.ComponentProps<typeof Modal>,
Omit<VariantProps<(typeof DialogStyles)["Modal"]>, "isSheet"> {}
export function DialogModal({
className,
side = "center",
...props
}: DialogModalProps) {
return (
<Modal
{...props}
className={(values) =>
DialogStyles.Modal({
side,
className:
typeof className === "function" ? className(values) : className,
})
}
data-side={side}
/>
);
}
export interface DialogContentProps
extends React.ComponentProps<typeof DialogPrimitive> {}
export function DialogContent({ className, ...props }: DialogContentProps) {
return (
<DialogPrimitive
{...props}
className={DialogStyles.Content({ className })}
/>
);
}
export interface DialogHeaderProps extends React.ComponentProps<"header"> {}
export function DialogHeader({ className, ...props }: DialogHeaderProps) {
return (
<header
{...props}
data-slot="dialog-header"
className={DialogStyles.Header({ className })}
/>
);
}
export interface DialogFooterProps extends React.ComponentProps<"footer"> {}
export function DialogFooter({ className, ...props }: DialogFooterProps) {
return <footer {...props} className={DialogStyles.Footer({ className })} />;
}
export interface DialogTitleProps
extends React.ComponentProps<typeof Heading> {}
export function DialogTitle({ className, ...props }: DialogTitleProps) {
return (
<Heading
level={2}
{...props}
data-slot="dialog-title"
className={DialogStyles.Title({ className })}
/>
);
}
export interface DialogDescriptionProps
extends React.ComponentProps<typeof Heading> {}
export function DialogDescription({
className,
...props
}: DialogDescriptionProps) {
return (
<Heading
level={3}
{...props}
className={DialogStyles.Description({ className })}
/>
);
}
export interface DialogCloseProps extends React.ComponentProps<typeof Button> {}
export function DialogClose({
children,
className,
...props
}: DialogCloseProps) {
return (
<Button
{...props}
slot="close"
className={DialogStyles.Close({ className })}
>
{children}
</Button>
);
}
export * from "./dialog";
export * as Dialog from "./namespace";
export * from "./styles";
export {
DialogRoot as Root,
DialogOverlay as Overlay,
DialogModal as Modal,
DialogContent as Content,
DialogHeader as Header,
DialogFooter as Footer,
DialogTitle as Title,
DialogDescription as Description,
DialogClose as Close,
} from "./dialog";
import { cva } from "~/lib/cva";
export const DialogStyles = {
Overlay: cva({
base: [
"fixed inset-0 z-50",
"entering:motion-opacity-in entering:motion-duration-500",
"exiting:motion-opacity-out exiting:motion-duration-300",
"has-[[data-side=center]]:motion-duration-150!",
],
variants: {
isBlurred: {
true: ["backdrop-blur"],
false: ["bg-black/15 dark:bg-black/60"],
},
},
defaultVariants: {
isBlurred: false,
},
}),
Modal: cva({
base: [
"data-[side=center]:motion-duration-150! fixed z-50 w-full bg-bg p-6 shadow-lg outline-none",
"entering:motion-opacity-in entering:motion-duration-500",
"exiting:motion-opacity-out exiting:motion-duration-300",
"sm:rounded-lg",
],
variants: {
side: {
top: [
"inset-x-0 top-0 left-0 border-b",
"entering:-motion-translate-y-in-100",
"exiting:-motion-translate-y-out-100",
],
bottom: [
"inset-x-0 bottom-0 border-t ease-in-out",
"entering:motion-translate-y-in-100",
"exiting:motion-translate-y-out-100",
],
left: [
"inset-y-0 left-0 h-full w-3/4 border-r ease-in-out sm:max-w-sm",
"entering:-motion-translate-x-in-100",
"exiting:-motion-translate-x-out-100",
],
right: [
"inset-y-0 right-0 h-full w-3/4 border-l ease-in-out sm:max-w-sm",
"entering:motion-translate-x-in-100",
"exiting:motion-translate-x-out-100",
],
center: [
"-translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 grid max-w-lg gap-4 border",
"entering:motion-scale-in-95",
"exiting:motion-scale-out-95",
],
},
},
defaultVariants: {
side: "center",
},
}),
Content: cva({
base: ["outline-none"],
}),
Close: cva({
base: [
"absolute top-4 right-4 size-4 rounded-sm opacity-70 outline-none ring-offset-bg transition",
"hover:opacity-100",
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
"disabled:pointer-events-none",
],
}),
Header: cva({
base: ["flex flex-col space-y-1.5 text-center", "sm:text-left"],
}),
Footer: cva({
base: ["flex flex-col-reverse", "sm:flex-row sm:justify-end sm:space-x-2"],
}),
Title: cva({
base: ["font-semibold text-lg leading-none tracking-tight"],
}),
Description: cva({
base: ["text-muted-fg text-sm"],
}),
};
Update the import paths to match your project setup.
Usage
Single import
import { Button } from "~/components/ui/button";
import { Dialog } from "~/components/ui/dialog";
<Dialog.Root>
<Button>Open</Button>
<Dialog.Overlay>
<Dialog.Modal>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Are you absolutely sure?</Dialog.Title>
<Dialog.Description>
This action cannot be undone. This will permanently delete your
account and remove your data from our servers.
</Dialog.Description>
</Dialog.Header>
</Dialog.Content>
</Dialog.Modal>
</Dialog.Overlay>
</Dialog.Root>
Multiple imports
import { Button } from "~/components/ui/button";
import {
DialogContent,
DialogDescription,
DialogHeader,
DialogModal,
DialogOverlay,
DialogRoot,
DialogTitle,
} from "~/components/ui/dialog";
<DialogRoot>
<Button>Open</Button>
<DialogOverlay>
<DialogModal>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete your
account and remove your data from our servers.
</DialogDescription>
</DialogHeader>
</DialogContent>
</DialogModal>
</DialogOverlay>
</DialogRoot>
Examples
Dialog
Preview
Code
Alert Dialog
Use the role="alertdialog"{:tsx}
prop on the <Dialog.Content>{:tsx}
element to make an alert dialog.
Also, make sure to set the isDismissable
prop to false{:ts}
on the <Dialog.Overlay>{:tsx}
or <Dialog.Modal>{:tsx}
to prevent the dialog from being dismissed by clicking outside of it.
Preview
Code
Sheet
Use the side{:tsx}
prop on the <Dialog.Modal>{:tsx}
element to make a sheet dialog.
Preview
Code
API Reference
Root
Prop | Type | Default |
---|---|---|
defaultOpen | boolean | false |
isOpen | boolean | false |
onOpenChange | function |
Content
Prop | Type | Default |
---|---|---|
side | enum | "center" |
role | enum | "dialog" |
isBlurred | boolean | false |
isDismissable | boolean | true |
isKeyboardDismissDisabled | boolean | false |