Dialog
Modal overlay anchored at viewport center, built on Radix Dialog with header, footer, and built-in close button.
Overview
Dialog is the right primitive for focused decisions or short flows that need to interrupt the user's context: confirmations, single-step forms, detail cards. It ships with overlay, content, header, footer, title, description, and a built-in close button. For longer flows that don't need full focus use Sheet; for inline contextual content use Popover.
The close button is on by default. Set showCloseButton={false} when the dialog renders its own dismiss affordance, or pass showCloseButton on DialogFooter to inject a styled "Close" button there instead.
Preview
'use client';import { Button } from '@gremorie/rx-forms';import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger,} from '@gremorie/rx-overlays';export function DialogPreview() { return ( <Dialog> <DialogTrigger asChild> <Button>Open dialog</Button> </DialogTrigger> <DialogContent> <DialogHeader> <DialogTitle>Edit profile</DialogTitle> <DialogDescription> Make changes to your profile here. </DialogDescription> </DialogHeader> <p className="text-sm">Form fields would go here.</p> <DialogFooter> <Button>Save</Button> </DialogFooter> </DialogContent> </Dialog> );}Anatomy
Dialog Radix root holding open state
├─ DialogTrigger element that opens the dialog
└─ DialogContent centered, portalled panel with overlay + close button
├─ DialogHeader wraps title + description
│ ├─ DialogTitle accessible headline
│ └─ DialogDescription accessible body text
└─ DialogFooter action row (optional built-in close button)
└─ DialogClose closes the dialog from any descendantInstallation
bash npx gremorie@latest add rx-dialog bash pnpm dlx gremorie@latest add rx-dialog bash yarn dlx gremorie@latest add rx-dialog bash bunx --bun gremorie@latest add rx-dialog Usage
import { Button } from "@gremorie/rx-forms";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@gremorie/rx-overlays";
export function Example() {
return (
<Dialog>
<DialogTrigger asChild>
<Button>Open dialog</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</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}The Angular edition of this component ships from source today (see the workbench for the side-by-side); its registry entry is coming next.
API
<Dialog>
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | - | Controlled open state. |
defaultOpen | boolean | false | Initial open state when uncontrolled. |
onOpenChange | (open: boolean) => void | - | Fired when open state changes. |
modal | boolean | true | Disable outside interaction while open. |
Extends Radix Dialog.Root. Renders no DOM by itself.
<DialogTrigger>
Extends Radix Dialog.Trigger. Pair with asChild to forward styles to a Button or any custom trigger.
<DialogContent>
| Prop | Type | Default | Description |
|---|---|---|---|
showCloseButton | boolean | true | Renders the top-right X close button. Disable when supplying a custom dismiss. |
Wraps Radix Dialog.Content inside a Portal with the overlay. Centered at 50/50, capped at sm:max-w-lg, animated open/close.
<DialogHeader>, <DialogFooter>
Plain layout containers (div). Header stacks title and description; footer is flex-col-reverse on mobile, flex-row justify-end on sm+.
DialogFooter accepts showCloseButton (boolean, default false) which appends a styled outline "Close" button wired to DialogClose.
<DialogTitle>, <DialogDescription>
Map to Radix Dialog.Title and Dialog.Description. Both are required by Radix; render with className="sr-only" if you need to hide them visually.
<DialogClose>, <DialogOverlay>, <DialogPortal>
Pass-through wrappers over their Radix counterparts with data-slot attributes. Use DialogClose asChild to dismiss from any custom button inside the content.
Composition
<Dialog>owns the open/close state.<DialogTrigger asChild>wraps any focusable element (typically aButton).<DialogContent>mounts via Portal, draws the overlay, and renders the centered card.- Inside content: header (title + description), body, footer (cancel + primary action).
- Dismissal: the built-in X covers the common case; supplement with explicit
DialogClosebuttons for destructive or stateful flows.
Variations
Form dialog
Embed form fields directly in the body. The dialog traps focus while open;
Cancel dismisses via DialogClose, and submit closes via state.
'use client';import { Button, Input, Label } from '@gremorie/rx-forms';import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger,} from '@gremorie/rx-overlays';export function DialogFormPreview() { return ( <Dialog> <DialogTrigger asChild> <Button>Edit profile</Button> </DialogTrigger> <DialogContent> <DialogHeader> <DialogTitle>Edit profile</DialogTitle> <DialogDescription> Update your details. Changes are saved when you click Save. </DialogDescription> </DialogHeader> <div className="grid gap-4 py-2"> <div className="grid gap-2"> <Label htmlFor="name">Name</Label> <Input id="name" defaultValue="Bruno Kalvner" /> </div> <div className="grid gap-2"> <Label htmlFor="username">Username</Label> <Input id="username" defaultValue="@kalvner" /> </div> </div> <DialogFooter> <DialogClose asChild> <Button variant="outline">Cancel</Button> </DialogClose> <Button>Save changes</Button> </DialogFooter> </DialogContent> </Dialog> );}<DialogContent>
<form onSubmit={handleSubmit}>
<DialogHeader>
<DialogTitle>Invite teammate</DialogTitle>
<DialogDescription>They'll get an email invite.</DialogDescription>
</DialogHeader>
<Field className="my-4">
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" type="email" required />
</Field>
<DialogFooter>
<Button type="submit">Send invite</Button>
</DialogFooter>
</form>
</DialogContent>Confirmation with explicit Cancel
For destructive but reversible actions, pair the X close with an explicit Cancel next to the primary Delete.
<Dialog>
<DialogTrigger asChild>
<Button variant="destructive">Delete project</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Delete project?</DialogTitle>
<DialogDescription>
All tasks, comments and attachments will be removed.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Cancel</Button>
</DialogClose>
<Button variant="destructive">Delete</Button>
</DialogFooter>
</DialogContent>
</Dialog>Without close button
Hide the X when the dialog is fully driven by the footer (e.g. multi-step wizard where back/next manage progression).
<DialogContent showCloseButton={false}>
{/* Custom step header here */}
</DialogContent>Accessibility
- Role:
role="dialog"witharia-modal="true"(defaultmodalprop). - Keyboard:
Esccloses;Tabcycles focus within the content;Shift+Tabcycles backwards; focus is trapped while open. - Focus management: focus moves to the first focusable element on open and returns to the trigger on close.
- Title:
DialogTitleis mandatory; without it Radix logs a warning. Usesr-onlyto hide visually. - Description: links to content via
aria-describedbyautomatically. - Reduced motion: open/close animations honor
prefers-reduced-motionvia the underlying Tailwind animate utilities.
Related
- Alert Dialog - interruptive confirmation that requires an explicit action.
- Sheet - lateral panel for longer flows.
- Drawer - mobile bottom sheet with gesture dismiss.
- Popover - inline anchored overlay for contextual UI.