Unified UI

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 dialog
pnpm dlx @work-rjkashyap/unified-ui add dialog
npx @work-rjkashyap/unified-ui add dialog
bunx @work-rjkashyap/unified-ui add dialog

If 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-ui
pnpm add @work-rjkashyap/unified-ui
yarn add @work-rjkashyap/unified-ui
bun add @work-rjkashyap/unified-ui

Anatomy

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.

SizeMax WidthUse Case
sm480pxSimple confirmations, short forms
md560pxMost forms and content dialogs
lg720pxComplex forms, settings, data tables
full100vwFull-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

PropTypeDefaultDescription
openbooleanControlled open state.
defaultOpenbooleanDefault open state (uncontrolled).
onOpenChange(open: boolean) => voidCallback when the open state changes.
modalbooleantrueWhether the dialog blocks interaction with the rest of the page.

DialogContent

PropTypeDefaultDescription
size"sm" | "md" | "lg" | "full""md"Max-width of the dialog panel.
showClosebooleantrueWhether to show the close button in the top-right corner.
classNamestringAdditional CSS classes.

DialogHeader

PropTypeDefaultDescription
classNamestringAdditional CSS classes.

DialogBody

PropTypeDefaultDescription
classNamestringAdditional CSS classes.

DialogFooter

PropTypeDefaultDescription
classNamestringAdditional CSS classes.

DialogTitle

PropTypeDefaultDescription
classNamestringAdditional CSS classes.

DialogDescription

PropTypeDefaultDescription
classNamestringAdditional 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 Escape closes 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 to DialogTitle.
  • aria-describedby — Automatically linked to DialogDescription.
  • The close button has aria-label="Close".
  • Uses z-modal for content and z-overlay for the backdrop.
  • Uses rounded-lg per project guidelines for dialog panels.

Design Tokens

TokenUsage
--surface-raisedDialog panel background
--overlayBackdrop overlay color
--radius-lgDialog border radius
--z-modalZ-index for dialog content
--z-overlayZ-index for backdrop
--shadow-lgDialog panel shadow

On this page