import Image from "next/image";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export function DialogDemo() {
return (
<Dialog>
<DialogTrigger render={<Button variant="outline">Open dialog</Button>} />
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Edit Content</DialogTitle>
<DialogDescription>
Update the details below and click save to apply changes.
</DialogDescription>
</DialogHeader>
<div className="relative aspect-video w-full overflow-hidden">
<Image
src="/images/dialog-demo-image.jpg"
alt="dialog-placeholder"
priority
className="rounded-md"
objectFit="cover"
fill
/>
</div>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-3">
<Label htmlFor="title">Title</Label>
<Input id="title" value="Sample Title" className="col-span-3" />
</div>
<div className="flex flex-col gap-3">
<Label htmlFor="description">Description</Label>
<Input
id="description"
value="Sample Description"
className="col-span-3"
/>
</div>
</div>
<DialogFooter>
<Button type="submit">Save changes</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}Installation
npx shadcn@latest add @eo-n/dialogUsage
Import all parts and piece them together.
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";<Dialog>
<DialogTrigger>Open</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you sure you want to proceed?</DialogTitle>
<DialogDescription>
This action may have permanent effects. Please confirm if you want to
continue.
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>Examples
With Custom Close Button
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
export function DialogCustomCloseButton() {
return (
<Dialog>
<DialogTrigger render={<Button variant="outline">Open dialog</Button>} />
<DialogContent>
<DialogHeader>
<DialogTitle>Confirm Your Action</DialogTitle>
<DialogDescription>
This operation will update your settings and may affect your current
configuration. Please confirm if you wish to proceed.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose render={<Button variant="outline">Cancel</Button>} />
<Button variant="default">Confirm</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}Without Close Icon
import Link from "next/link";
import { Radius } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export function DialogWithNoXButton() {
return (
<Dialog>
<DialogTrigger render={<Button variant="default">Sign in</Button>} />
<DialogContent hideCloseIcon className="sm:max-w-[415px]">
<DialogHeader className="items-center px-10">
<div className="mb-2 grid place-items-center rounded-full border-2 p-3">
<Radius />
</div>
<DialogTitle>Welcome back</DialogTitle>
<DialogDescription className="text-center">
Enter your credentials to login to your account.
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-3">
<Label htmlFor="email">Email</Label>
<Input
id="email"
autoComplete="off"
placeholder="eo-n@gmail.com"
className="col-span-3"
/>
</div>
<div className="flex flex-col gap-3">
<Label htmlFor="password">Password</Label>
<Input
id="password"
autoComplete="off"
placeholder="*********"
className="col-span-3"
/>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Checkbox id="remember-me" />
<label
htmlFor="remember-me"
className="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Remember me
</label>
</div>
<Link
prefetch
href="/"
className="text-sm text-blue-400 underline underline-offset-4"
>
Forgot password?
</Link>
</div>
</div>
<DialogFooter className="gap-3 sm:flex-col">
<Button variant="default" className="w-full">
Sign in
</Button>
<div className="before:bg-border after:bg-border flex items-center gap-3 before:h-px before:flex-1 after:h-px after:flex-1">
<span className="text-muted-foreground text-xs">Or</span>
</div>
<Button variant="outline" className="w-full [&_svg]:size-4">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
className="size-2"
>
<path
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
fill="currentColor"
/>
</svg>
Login with Google
</Button>
<div className="text-muted-foreground text-center text-sm">
Don't have an account?{" "}
<Link
prefetch
href="/"
className="text-blue-400 underline underline-offset-4"
>
Sign up
</Link>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
);
}Nested Dialogs
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
export function DialogNested() {
return (
<Dialog>
<DialogTrigger render={<Button variant="outline">Settings</Button>} />
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Account Settings</DialogTitle>
<DialogDescription>
Manage your account preferences.
</DialogDescription>
</DialogHeader>
<div className="py-2">
<div className="flex items-center justify-between">
<span>Email notifications</span>
<span>Enabled</span>
</div>
</div>
<DialogFooter>
<Dialog>
<DialogTrigger render={<Button variant="ghost">Advanced</Button>} />
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Advanced Settings</DialogTitle>
<DialogDescription>
Configure advanced options.
</DialogDescription>
</DialogHeader>
<div className="py-2">
<div className="flex items-center justify-between">
<span>Developer mode</span>
<span>Disabled</span>
</div>
</div>
<DialogFooter>
<DialogClose render={<Button>Save</Button>} />
</DialogFooter>
</DialogContent>
</Dialog>
<DialogClose render={<Button>Save</Button>} />
</DialogFooter>
</DialogContent>
</Dialog>
);
}Scrollable Content
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { ScrollArea } from "@/components/ui/scroll-area";
export function DialogScrollable() {
return (
<Dialog>
<DialogTrigger
render={<Button variant="outline">Terms of Service</Button>}
/>
<DialogContent className="sm:max-w-[415px]" flush>
<DialogHeader>
<DialogTitle>Terms of Service</DialogTitle>
<DialogDescription>
Read the terms and conditions carefully.
</DialogDescription>
</DialogHeader>
<ScrollArea className="my-2 mr-2 ml-4 flex-1">
<p className="pb-4 text-sm font-semibold">1. Introduction</p>
<p className="text-muted-foreground mb-4 text-sm leading-6">
Welcome to our application. By accessing or using our service, you
agree to be bound by these terms. If you do not agree to these
terms, you may not use the service.
</p>
<p className="pb-4 text-sm font-semibold">2. User Accounts</p>
<p className="text-muted-foreground mb-4 text-sm leading-6">
You are responsible for maintaining the security of your account and
password. The company cannot and will not be liable for any loss or
damage from your failure to comply with this security obligation.
</p>
<p className="pb-4 text-sm font-semibold">3. Content</p>
<p className="text-muted-foreground mb-4 text-sm leading-6">
Our service allows you to post, link, store, share and otherwise
make available certain information, text, graphics, videos, or other
material. You are responsible for the content that you post to the
service, including its legality, reliability, and appropriateness.
</p>
<p className="pb-4 text-sm font-semibold">4. Termination</p>
<p className="text-muted-foreground mb-4 text-sm leading-6">
We may terminate or suspend access to our service immediately,
without prior notice or liability, for any reason whatsoever,
including without limitation if you breach the Terms.
</p>
<p className="pb-4 text-sm font-semibold">5. Changes</p>
<p className="text-muted-foreground text-sm leading-6">
We reserve the right, at our sole discretion, to modify or replace
these Terms at any time. If a revision is material we will try to
provide at least 30 days notice prior to any new terms taking
effect. What constitutes a material change will be determined at our
sole discretion.
</p>
</ScrollArea>
<DialogFooter>
<Button type="submit">Accept terms</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}Open On Menu
In order to open a dialog using a menu, control the dialog state and open it imperatively using the onClick handler on the menu item.
Return Focus Properly
Make sure to also use the dialog’s finalFocus prop to return focus back
to the menu trigger.
import * as React from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../ui/dialog";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuPositioner,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
export default function DropdownMenuRadio() {
const menuTriggerRef = React.useRef<HTMLButtonElement>(null);
const [dialogOpen, setDialogOpen] = React.useState(false);
return (
<React.Fragment>
<DropdownMenu>
{/* Set the trigger ref */}
<DropdownMenuTrigger ref={menuTriggerRef}>Open</DropdownMenuTrigger>
<DropdownMenuPositioner>
<DropdownMenuContent>
{/* Open the dialog when the menu item is clicked */}
<DropdownMenuItem onClick={() => setDialogOpen(true)}>
Open dialog
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuPositioner>
</DropdownMenu>
{/* Control the dialog state */}
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
{/* Return focus to the menu trigger when the dialog is closed */}
<DialogContent finalFocus={menuTriggerRef}>
<DialogHeader>
<DialogTitle>Are you sure you want to proceed?</DialogTitle>
<DialogDescription>
This action may have permanent effects. Please confirm if you want
to continue.
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
</React.Fragment>
);
}API Reference
Content
Contains content to be rendered in the open dialog.
| Prop | Type | Default |
|---|---|---|
hideCloseIcon? | boolean | false |
flush? | boolean | false |