Docs
Dialog
Dialog
A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.
Loading...
Installation
Add the following components:
Copy and paste the following code into your project.
"use client";
import type { JSXElementConstructor, ReactElement } from "react";
import { Slot } from "@radix-ui/react-slot";
import type { VariantProps } from "cva";
import { cva } from "cva";
import { AlertTriangle, HelpCircle, Info, X } from "lucide-react";
import type { ModalRenderProps } from "react-aria-components";
import {
Heading,
Modal,
ModalOverlay,
Dialog as RaDialog,
DialogTrigger as RaDialogTrigger,
} from "react-aria-components";
import { Button, ButtonIcon } from "@/components/ui/button";
import { autoRef, cn, withRenderProps } from "@/lib/utils";
/* -------------------------------------------------------------------------- */
/* Variants */
/* -------------------------------------------------------------------------- */
export const dialogVariants = {
overlay: cva({
base: cn(
"fixed inset-0 z-50 flex min-h-full items-center justify-center overflow-y-auto bg-black/25 p-4 text-center backdrop-blur",
"s-entering:animate-in s-entering:fade-in s-entering:duration-300 s-entering:ease-out",
"s-exiting:animate-out s-exiting:fade-out s-exiting:duration-200 s-exiting:ease-in",
),
}),
root: cva({
base: cn(
"w-full overflow-hidden rounded-lg text-left align-middle shadow-lg",
"sm:max-h-[90%]",
"s-entering:animate-in s-entering:zoom-in-95 s-entering:duration-300",
"s-exiting:animate-out s-exiting:zoom-out-95 s-exiting:duration-200",
),
variants: {
size: {
default: "sm:max-w-lg",
lg: "sm:max-w-3xl",
xl: "sm:max-w-6xl",
},
},
defaultVariants: {
size: "default",
},
}),
rootInner: cva({
base: cn("relative outline-none flex flex-col h-full bg-white"),
}),
header: cva({
base: cn("flex flex-col space-y-1.5 text-center sm:text-left shrink-0"),
}),
title: cva({
base: cn("px-6 pt-6 text-lg font-semibold leading-none tracking-tight"),
}),
description: cva({
base: "px-6 text-sm text-muted-foreground",
}),
body: cva({
base: cn("flex flex-col overflow-y-auto px-6 py-6"),
variants: {
isDescription: {
true: "text-muted-foreground",
false: "",
},
},
defaultVariants: {
isDescription: false,
},
}),
footer: cva({
base: cn(
"flex flex-col-reverse gap-2 px-6 pb-6 sm:flex-row sm:justify-end sm:gap-0 sm:space-x-2 shrink-0",
),
}),
};
/* -------------------------------------------------------------------------- */
/* Components */
/* -------------------------------------------------------------------------- */
/* --------------------------------- Trigger -------------------------------- */
export const DialogTrigger = RaDialogTrigger;
/* ---------------------------------- Root ---------------------------------- */
export type DialogProps = Omit<
React.ComponentPropsWithRef<typeof Modal>,
"children"
> &
VariantProps<typeof dialogVariants.root> & {
children?:
| React.ReactNode
| ((values: ModalRenderProps & { close: () => void }) => React.ReactNode);
} & {
isAlert?: boolean;
icon?:
| "alert"
| "info"
| "question"
| ReactElement<any, string | JSXElementConstructor<any>>;
["aria-label"]?: string;
classNames?: {
icon?: string;
};
};
export const Dialog = autoRef(
({
className,
children,
size,
isAlert,
icon,
"aria-label": ariaLabel,
isDismissable = true,
classNames,
...props
}: DialogProps) => {
let dialogIcon: React.ReactNode;
switch (icon) {
case "alert": {
dialogIcon = <AlertTriangle />;
break;
}
case "info": {
dialogIcon = <Info />;
break;
}
case "question": {
dialogIcon = <HelpCircle />;
break;
}
default:
dialogIcon = icon;
}
return (
<ModalOverlay
{...props}
className={dialogVariants.overlay()}
isDismissable={isDismissable}
>
<Modal
className={cn(dialogVariants.root({ size }), className)}
{...props}
>
{(modalValues) => (
<RaDialog
aria-label={ariaLabel}
role={isAlert ? "alertdialog" : "dialog"}
className={cn(dialogVariants.rootInner())}
>
{({ close }) => (
<>
{icon ? (
<Slot className="absolute right-6 top-6 h-6 w-6 opacity-30">
{dialogIcon}
</Slot>
) : (
<Button
icon
variant="ghost"
className={cn("absolute right-4 top-4", classNames?.icon)}
onPress={close}
>
<ButtonIcon icon={<X />} />
</Button>
)}
{withRenderProps(children)({ ...modalValues, close })}
</>
)}
</RaDialog>
)}
</Modal>
</ModalOverlay>
);
},
);
/* --------------------------------- Overlay -------------------------------- */
export type DialogOverlayProps = React.ComponentPropsWithRef<
typeof ModalOverlay
>;
export const DialogOverlay = autoRef(
({ className, ...props }: DialogOverlayProps) => {
return (
<ModalOverlay
className={cn(dialogVariants.overlay(), className)}
{...props}
/>
);
},
);
/* --------------------------------- Header --------------------------------- */
export type DialogHeaderProps = React.ComponentPropsWithRef<"div">;
export const DialogHeader = autoRef(
({ className, ...props }: DialogHeaderProps) => {
return (
<div className={cn(dialogVariants.header(), className)} {...props} />
);
},
);
/* ---------------------------------- Title --------------------------------- */
export type DialogTitleProps = React.ComponentPropsWithRef<typeof Heading>;
export const DialogTitle = autoRef(
({ className, ...props }: DialogTitleProps) => {
return (
<Heading
slot="title"
className={cn(dialogVariants.title(), className)}
{...props}
/>
);
},
);
/* ------------------------------- Description ------------------------------ */
export type DialogDescriptionProps = React.ComponentPropsWithRef<"p">;
export const DialogDescription = autoRef(
({ className, ...props }: DialogDescriptionProps) => {
return (
<p className={cn(dialogVariants.description(), className)} {...props} />
);
},
);
/* ---------------------------------- Body ---------------------------------- */
export type DialogBodyProps = React.ComponentPropsWithRef<"div"> &
VariantProps<typeof dialogVariants.body>;
export const DialogBody = autoRef(
({ className, isDescription, ...props }: DialogBodyProps) => {
return (
<div
className={cn(dialogVariants.body({ isDescription }), className)}
{...props}
/>
);
},
);
/* --------------------------------- Footer --------------------------------- */
export type DialogFooterProps = React.ComponentPropsWithRef<"div">;
export const DialogFooter = autoRef(
({ className, ...props }: DialogFooterProps) => {
return (
<div className={cn(dialogVariants.footer(), className)} {...props} />
);
},
);
Update the import paths to match your project setup.
Examples
Dialog from Dropdown Menu
Loading...
Scrollable dialog body
Add h-full
to Dialog
and grow
to DialogBody
.
Loading...