"use client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Field, FieldGroup } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export function DialogDemo() {
return (
<Dialog>
<form>
<DialogTrigger render={<Button variant="tertiary" />}>
Open Dialog
</DialogTrigger>
<DialogContent className="sm:max-w-sm">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're
done.
</DialogDescription>
</DialogHeader>
<FieldGroup>
<Field>
<Label htmlFor="name-1">Name</Label>
<Input
variant="secondary"
id="name-1"
name="name"
defaultValue="Maged Ibrahim"
/>
</Field>
<Field>
<Label htmlFor="username-1">Username</Label>
<Input
variant="secondary"
id="username-1"
name="username"
defaultValue="@0xMaqed"
/>
</Field>
</FieldGroup>
<DialogFooter>
<DialogClose render={<Button variant="tertiary" />}>
Cancel
</DialogClose>
<Button type="submit">Save changes</Button>
</DialogFooter>
</DialogContent>
</form>
</Dialog>
);
}
pnpm dlx shadcn@latest add https://herocn.dev/r/dialog.jsonimport {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"<Dialog>
<DialogTrigger>Open</DialogTrigger>
<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>
</Dialog>Use the following composition to build a Dialog:
Dialog
├── DialogTrigger
└── DialogContent
├── DialogHeader
│ ├── DialogTitle
│ └── DialogDescription
└── DialogFooterUse overlayVariant on DialogContent to choose an opaque dim (opaque), a blurred backdrop (blur), or transparent overlay (transparent).
"use client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
export function DialogOverlayVariant() {
return (
<div className="flex flex-wrap gap-2">
<Dialog>
<DialogTrigger render={<Button variant="tertiary" />}>
Opaque
</DialogTrigger>
<DialogContent overlayVariant="opaque" className="sm:max-w-sm">
<DialogHeader>
<DialogTitle>Opaque overlay</DialogTitle>
<DialogDescription>
Default dimmed backdrop without extra blur on the page behind the
dialog.
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
<Dialog>
<DialogTrigger render={<Button variant="tertiary" />}>
Blur
</DialogTrigger>
<DialogContent overlayVariant="blur" className="sm:max-w-sm">
<DialogHeader>
<DialogTitle>Blurred overlay</DialogTitle>
<DialogDescription>
Backdrop blur so content behind the dialog is visibly softened.
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
<Dialog>
<DialogTrigger render={<Button variant="tertiary" />}>
Transparent
</DialogTrigger>
<DialogContent overlayVariant="transparent" className="sm:max-w-sm">
<DialogHeader>
<DialogTitle>Transparent overlay</DialogTitle>
<DialogDescription>
No dimmed backdrop — the page stays fully visible behind the
dialog surface.
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
</div>
);
}
Replace the default close control with your own button.
"use client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export function DialogCloseButton() {
return (
<Dialog>
<DialogTrigger render={<Button variant="tertiary" />}>
Share
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Share link</DialogTitle>
<DialogDescription>
Anyone who has this link will be able to view this.
</DialogDescription>
</DialogHeader>
<div className="flex items-center gap-2">
<div className="grid flex-1 gap-2">
<Label htmlFor="link" className="sr-only">
Link
</Label>
<Input
variant="secondary"
id="link"
defaultValue="https://herocn.dev/docs/installation"
readOnly
/>
</div>
</div>
<DialogFooter className="sm:justify-start">
<DialogClose render={<Button type="button" />}>Close</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
Use showCloseButton={false} to hide the close button.
"use client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
export function DialogNoCloseButton() {
return (
<Dialog>
<DialogTrigger render={<Button variant="tertiary" />}>
No Close Button
</DialogTrigger>
<DialogContent showCloseButton={false}>
<DialogHeader>
<DialogTitle>No Close Button</DialogTitle>
<DialogDescription>
This dialog doesn't have a close button in the top-right
corner.
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
);
}
Keep actions visible while the content scrolls.
"use client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
export function DialogStickyFooter() {
return (
<Dialog>
<DialogTrigger render={<Button variant="tertiary" />}>
Sticky Footer
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Sticky Footer</DialogTitle>
<DialogDescription>
This dialog has a sticky footer that stays visible while the content
scrolls.
</DialogDescription>
</DialogHeader>
<div className="no-scrollbar -mx-4 max-h-[50vh] overflow-y-auto px-4">
{Array.from({ length: 10 }).map((_, index) => (
<p key={index} className="mb-4 leading-normal">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
))}
</div>
<DialogFooter>
<DialogClose render={<Button variant="tertiary" />}>
Close
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
Long content can scroll while the header stays in view.
"use client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
export function DialogScrollableContent() {
return (
<Dialog>
<DialogTrigger render={<Button variant="tertiary" />}>
Scrollable Content
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Scrollable Content</DialogTitle>
<DialogDescription>
This is a dialog with scrollable content.
</DialogDescription>
</DialogHeader>
<div className="no-scrollbar -mx-4 max-h-[50vh] overflow-y-auto px-4">
{Array.from({ length: 10 }).map((_, index) => (
<p key={index} className="mb-4 leading-normal">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
))}
</div>
</DialogContent>
</Dialog>
);
}
To enable RTL support in herocn, see the RTL configuration guide.
"use client";
import {
type Translations,
useTranslation,
} from "@/components/language-selector";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Field, FieldGroup } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
const translations: Translations = {
en: {
dir: "ltr",
values: {
openDialog: "Open Dialog",
editProfile: "Edit profile",
description:
"Make changes to your profile here. Click save when you're done.",
name: "Name",
username: "Username",
cancel: "Cancel",
saveChanges: "Save changes",
},
},
ar: {
dir: "rtl",
values: {
openDialog: "فتح الحوار",
editProfile: "تعديل الملف الشخصي",
description:
"قم بإجراء تغييرات على ملفك الشخصي هنا. انقر فوق حفظ عند الانتهاء.",
name: "الاسم",
username: "اسم المستخدم",
cancel: "إلغاء",
saveChanges: "حفظ التغييرات",
},
},
he: {
dir: "rtl",
values: {
openDialog: "פתח דיאלוג",
editProfile: "ערוך פרופיל",
description: "בצע שינויים בפרופיל שלך כאן. לחץ על שמור כשתסיים.",
name: "שם",
username: "שם משתמש",
cancel: "בטל",
saveChanges: "שמור שינויים",
},
},
};
export function DialogRtl() {
const { dir, language, t } = useTranslation(translations, "ar");
return (
<div lang={language} dir={dir}>
<Dialog>
<form>
<DialogTrigger render={<Button variant="tertiary" />}>
{t.openDialog}
</DialogTrigger>
<DialogContent
dir={dir}
data-lang={dir === "rtl" ? language : undefined}
className="sm:max-w-sm"
>
<DialogHeader>
<DialogTitle>{t.editProfile}</DialogTitle>
<DialogDescription>{t.description}</DialogDescription>
</DialogHeader>
<FieldGroup>
<Field>
<Label htmlFor="name-rtl">{t.name}</Label>
<Input
variant="secondary"
id="name-rtl"
name="name"
defaultValue="Maged Ibrahim"
/>
</Field>
<Field>
<Label htmlFor="username-rtl">{t.username}</Label>
<Input
variant="secondary"
id="username-rtl"
name="username"
defaultValue="@0xMaqed"
/>
</Field>
</FieldGroup>
<DialogFooter>
<DialogClose render={<Button variant="tertiary" />}>
{t.cancel}
</DialogClose>
<Button type="submit">{t.saveChanges}</Button>
</DialogFooter>
</DialogContent>
</form>
</Dialog>
</div>
);
}
See the Base UI Dialog documentation for more information.