Dialog
A modal dialog component with focus trap, slot composition, and multiple sizes. Built on Radix UI.
Basic
A production-ready modal dialog with focus trap, keyboard dismissal, and slot-based composition. Built on Radix UI for full accessibility.
Installation
Install the component via the CLI in one command.
npx @work-rjkashyap/unified-ui add dialogpnpm dlx @work-rjkashyap/unified-ui add dialognpx @work-rjkashyap/unified-ui add dialogbunx @work-rjkashyap/unified-ui add dialogIf you haven't initialized your project yet, run npx @work-rjkashyap/unified-ui init first. See the CLI docs for details.
Or install the full package
Use this approach if you prefer to install the entire design system as a dependency instead of copying individual components.
npm install @work-rjkashyap/unified-uipnpm add @work-rjkashyap/unified-uiyarn add @work-rjkashyap/unified-uibun add @work-rjkashyap/unified-uiAnatomy
import {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogBody,
DialogFooter,
DialogTitle,
DialogDescription,
DialogClose,
Button,
Input,
} from "@work-rjkashyap/unified-ui";Basic Usage
The Dialog uses a trigger-based pattern. Wrap a button in DialogTrigger and the content in DialogContent. Radix UI handles open/close state, focus trapping, and scroll locking automatically.
<Dialog>
<DialogTrigger asChild>
<Button>Open</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogDescription>Optional description text.</DialogDescription>
</DialogHeader>
<DialogBody>
<p>Dialog content goes here.</p>
</DialogBody>
<DialogFooter>
<DialogClose asChild>
<Button variant="secondary">Close</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>Sizes
The Dialog supports four size presets that control the max-width of the content panel.
| Size | Max Width | Use Case |
|---|---|---|
sm | 480px | Simple confirmations, short forms |
md | 560px | Most forms and content dialogs |
lg | 720px | Complex forms, settings, data tables |
full | 100vw | Full-screen overlays, media viewers |
Slot Composition
The Dialog uses a slot-based pattern with DialogHeader, DialogBody, and DialogFooter sub-components for consistent structure.
<DialogContent>
{/* Header — contains title and description */}
<DialogHeader>
<DialogTitle>Title</DialogTitle>
<DialogDescription>Description</DialogDescription>
</DialogHeader>
{/* Body — scrollable main content area */}
<DialogBody>
<p>Content goes here.</p>
</DialogBody>
{/* Footer — actions, typically right-aligned */}
<DialogFooter>
<DialogClose asChild>
<Button variant="secondary">Cancel</Button>
</DialogClose>
<Button>Save</Button>
</DialogFooter>
</DialogContent>Controlled Mode
Use the open and onOpenChange props for programmatic control.
const [open, setOpen] = useState(false);
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>Open</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Controlled Dialog</DialogTitle>
</DialogHeader>
<DialogBody>
<p>This dialog is controlled externally.</p>
</DialogBody>
<DialogFooter>
<Button
variant="primary"
onClick={() => {
// Perform action then close
handleSave();
setOpen(false);
}}
>
Save & Close
</Button>
</DialogFooter>
</DialogContent>
</Dialog>;Confirmation Pattern
A common pattern for destructive actions requiring user confirmation.
Without Close Button
By default, DialogContent renders a close button in the top-right corner. Set showClose={false} to hide it.
<DialogContent showClose={false}>
<DialogHeader>
<DialogTitle>No close button</DialogTitle>
</DialogHeader>
<DialogBody>
<p>The user must use the footer actions or press Escape.</p>
</DialogBody>
<DialogFooter>
<DialogClose asChild>
<Button variant="secondary">Dismiss</Button>
</DialogClose>
</DialogFooter>
</DialogContent>Form Dialog
Dialogs are commonly used to wrap forms. The DialogBody area scrolls independently when content overflows.
<Dialog>
<DialogTrigger asChild>
<Button>Create Project</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>New Project</DialogTitle>
<DialogDescription>
Fill in the details to create a new project.
</DialogDescription>
</DialogHeader>
<DialogBody>
<form className="flex flex-col gap-4">
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium">Project Name</label>
<Input placeholder="My awesome project" />
</div>
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium">Description</label>
<Textarea placeholder="What is this project about?" />
</div>
</form>
</DialogBody>
<DialogFooter>
<DialogClose asChild>
<Button variant="secondary">Cancel</Button>
</DialogClose>
<Button variant="primary">Create</Button>
</DialogFooter>
</DialogContent>
</Dialog>Props
Dialog
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | — | Controlled open state. |
defaultOpen | boolean | — | Default open state (uncontrolled). |
onOpenChange | (open: boolean) => void | — | Callback when the open state changes. |
modal | boolean | true | Whether the dialog blocks interaction with the rest of the page. |
DialogContent
| Prop | Type | Default | Description |
|---|---|---|---|
size | "sm" | "md" | "lg" | "full" | "md" | Max-width of the dialog panel. |
showClose | boolean | true | Whether to show the close button in the top-right corner. |
className | string | — | Additional CSS classes. |
DialogHeader
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Additional CSS classes. |
DialogBody
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Additional CSS classes. |
DialogFooter
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Additional CSS classes. |
DialogTitle
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Additional CSS classes. |
DialogDescription
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Additional CSS classes. |
DialogTrigger
Uses Radix UI's DialogTrigger API. Use asChild to render the trigger as your own element.
DialogClose
Uses Radix UI's DialogClose API. Use asChild to render the close action as your own element.
Accessibility
- Built on @radix-ui/react-dialog for full ARIA compliance.
- Focus trap — Focus is contained within the dialog when open (managed by Radix).
- Focus restoration — Focus returns to the trigger element when the dialog closes.
- Escape to close — Pressing
Escapecloses the dialog. - Click outside — Clicking the overlay backdrop closes the dialog.
- Scroll lock — Body scroll is locked while the dialog is open.
aria-labelledby— Automatically linked toDialogTitle.aria-describedby— Automatically linked toDialogDescription.- The close button has
aria-label="Close". - Uses
z-modalfor content andz-overlayfor the backdrop. - Uses
rounded-lgper project guidelines for dialog panels.
Design Tokens
| Token | Usage |
|---|---|
--surface-raised | Dialog panel background |
--overlay | Backdrop overlay color |
--radius-lg | Dialog border radius |
--z-modal | Z-index for dialog content |
--z-overlay | Z-index for backdrop |
--shadow-lg | Dialog panel shadow |