Dialog
Modal window with focus trap, backdrop blur, and accessible close. Use AlertDialog for destructive confirms.
Examples
RTL
Right-to-left layout support for languages such as Arabic and Hebrew.
Translations are AI-generated for demonstration and may be imperfect.
import { Button } from "@codefast/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@codefast/ui/dialog";
import { Field, FieldGroup } from "@codefast/ui/field";
import { Input } from "@codefast/ui/input";
import { Label } from "@codefast/ui/label";
import type { Translations } from "#/features/components-catalog/components/detail/language";
import { useTranslation } from "#/features/components-catalog/components/detail/language-context";
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, t, language } = useTranslation(translations, "ar");
return (
<Dialog>
<form>
<DialogTrigger asChild>
<Button variant="outline">{t.openDialog}</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-sm" dir={dir} data-lang={dir === "rtl" ? language : undefined}>
<DialogHeader>
<DialogTitle>{t.editProfile}</DialogTitle>
<DialogDescription>{t.description}</DialogDescription>
</DialogHeader>
<FieldGroup>
<Field>
<Label htmlFor="name-1">{t.name}</Label>
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
</Field>
<Field>
<Label htmlFor="username-1">{t.username}</Label>
<Input id="username-1" name="username" defaultValue="@vuongphan" />
</Field>
</FieldGroup>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">{t.cancel}</Button>
</DialogClose>
<Button type="submit">{t.saveChanges}</Button>
</DialogFooter>
</DialogContent>
</form>
</Dialog>
);
}
Scrollable Content
Long content can scroll while the header stays in view.
import { Button } from "@codefast/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@codefast/ui/dialog";
export function DialogScrollableContent() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Scrollable Content</Button>
</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>
);
}
Usage
The minimal import and composition — see Examples below for styled, real-world variants.
import { Button } from "@codefast/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@codefast/ui/dialog";
export function DialogUsage() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Edit profile</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>Make changes to your profile here.</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<Button>Save changes</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
Anatomy
How the parts nest — every slot the component exposes, in composition order.
Features
- DialogBody is a codefast addition over upstream Radix — long content scrolls on its own while DialogHeader/DialogFooter stay pinned, instead of the whole panel scrolling.
- DialogFooter's showCloseButton renders a ready-made "Close" button — no need to wire DialogClose asChild yourself.
- DialogContent's showCloseButton (default true) toggles the built-in corner × button.
- modal={false} lets content outside the dialog stay interactive instead of becoming inert.
API reference
Props for each part of the component. All native element props are also forwarded.
Dialog
Root. Manages open state.
openbooleanThe controlled open state.
defaultOpenbooleanThe open state when initially rendered (uncontrolled).
Default
falseonOpenChange(open: boolean) => voidCalled when the open state changes.
modalbooleanWhen true, content outside the dialog is inert.
Default
true
DialogContent
The panel. Traps focus and renders in a portal.
onEscapeKeyDown(event) => voidIntercept the Escape-to-close behaviour.
Accessibility
Built to be keyboard-navigable and screen-reader friendly out of the box.
| Key | Function |
|---|---|
| Space | When the trigger is focused, opens the dialog. |
| Enter | When the trigger is focused, opens the dialog. |
| Tab | Cycles focus within the dialog (focus is trapped). |
| Shift+Tab | Cycles focus backwards within the dialog. |
| Esc | Closes the dialog and restores focus to the trigger. |
- Focus moves into the dialog on open and returns to the trigger on close.
- DialogTitle and DialogDescription are wired to aria-labelledby / aria-describedby.
- While open, the rest of the page is marked inert for assistive tech.
Guidelines
Conventions that keep usage consistent across an app.
Do
- Always include a DialogTitle, even if visually concise.
- Use a Dialog for focused tasks; use AlertDialog for destructive confirmations.
Don’t
- Don’t stack multiple dialogs on top of one another.
- Don’t put long, primary content in a dialog — use a page or a sheet instead.
Explore further
Ready to integrate?
Follow the Getting Started guide to install @codefast/ui, or browse the full component gallery.