Dialog
A modal surface that interrupts the flow for a focused task or message.
@garn/ui/dialogOn this page
Default
Trigger → titled dialog with footer actions.
Installation
garn is copy-in — the garn CLI writes this component's source into your project, so you own and can edit it. It resolves any base files and installs the npm dependencies below for you.
npx garn-ui add dialog- Registry
dialog- Deps
@radix-ui/react-dialoglucide-react- Registry deps
utilsframe
Import
import { Dialog, DialogTrigger, DialogContent, DialogTitle, DialogDescription, DialogHeader, DialogFooter, DialogClose } from "@garn/ui/dialog";Anatomy
The parts this component is built from — compose them to assemble it.
DialogRequiredRoot — owns the open state. No DOM of its own.
DialogTrigger[data-slot="dialog-trigger"]Opens the dialog (use asChild to wrap your own button).
DialogContentRequired[data-slot="dialog-content"]Focus-trapped surface (renders the overlay + a built-in close button).
DialogTitleRequired[data-slot="dialog-title"]Accessible name for the dialog (required; sr-only if visually hidden).
DialogDescription[data-slot="dialog-description"]Supplementary description, wired to aria-describedby.
DialogHeader[data-slot="dialog-header"]Layout wrapper for title + description.
DialogFooter[data-slot="dialog-footer"]Layout wrapper for actions (right-aligned on ≥sm).
DialogClose[data-slot="dialog-close"]Closes the dialog (e.g. a Cancel button).
Props
The component's public props and their types.
No component-specific props — this is a thin wrapper over its native element.
Styling
Target these data-slots and states, and remap these tokens, to restyle without forking the component.
[data-slot="dialog-content"]States
| Selector | State | Description |
|---|---|---|
[data-state=open] | open | Content + overlay mounted and animated in. |
[data-state=closed] | closed | Animating out before unmount. |
| Property | Token | Tier |
|---|---|---|
padding | --garn-pad-surface | semantic · air |
gap | --garn-gap-stack | semantic · air |
closeSize | --garn-control-h-xs | semantic · air |
motion | --garn-motion-fast | semantic |
When to use
Interrupt the flow for a focused task or message that needs attention before continuing — a short form, details, or confirmation.
Reach for something else when
- A destructive yes/no that must force an explicit choice (use alert-dialog)
- contextual, non-blocking content near a control (use popover)
- a large side panel (use sheet or drawer).
Overview
Dialog interrupts the flow for a focused task that needs attention before the user moves on — a short form, a detail view, a confirmation. It's a compound built on Radix: Dialog owns the open state, DialogTrigger opens it, and DialogContent is a focus-trapped surface that renders its own overlay and close button. Inside, DialogTitle (required), DialogDescription, and the DialogHeader/DialogFooter layout wrappers structure the content; DialogClose dismisses it.
The modal contract is the whole point: focus is trapped while open and returned to the trigger on close, the background is inert, and Escape closes. Pick Dialog when the task is short and self-contained. For a destructive confirmation that must not be dismissed by clicking away, use AlertDialog; for a long form or a side panel, use Sheet; for lightweight, non-blocking context anchored to a control, use Popover.
Guidelines
Always render a DialogTitle — it's the dialog's accessible name and the contract is enforced. If the design has no visible heading, keep the title and visually hide it (sr-only); don't drop it. Pair it with a DialogDescription whenever a sentence of context would help, so it wires to aria-describedby.
Keep dialogs short and single-purpose. A dialog is for one focused task; if it grows into a multi-step flow or a long scroll, that's a sign it should be a full page or a Sheet. Put the primary action in the DialogFooter as the one filled button, with Cancel/Close demoted beside it.
Don't stack or auto-open dialogs. Opening a dialog from inside a dialog traps focus in a confusing nest; resolve one before opening the next. Reserve dialogs for moments that genuinely warrant interrupting the user — overusing them for messages that a Sonner toast or inline Alert could carry trains people to dismiss them on reflex.
Best practices
- Always include a DialogTitle (sr-only if you don't want it visible).
- Keep the task short; put primary/secondary actions in a DialogFooter.
- Let Escape and the overlay close it — don't trap users.
- Don't stack dialogs or nest a dialog inside a dialog.
- Don't use a modal for a destructive confirm — alert-dialog is purpose-built for that.
Content guidelines
- Give the dialog a specific title that names the task (“Invite teammate”).
- Label the primary action by outcome and keep the dismiss action clearly secondary.
Troubleshooting
Console warning about a missing Description / Title; screen readers can't name the dialog.
Cause. DialogContent without a DialogTitle (Radix requires it).
Fix. Add a DialogTitle (wrap in an sr-only span if you don't want it visible); add DialogDescription to silence the description warning.
Accessibility
- Role
dialog- ARIA APG
- dialog (modal)
- Focus
- Focus trapped inside the content; returns to the trigger on close.
Accessibility requirements
Every DialogContent must contain a DialogTitle (wrap it in sr-only to hide it visually).
when DialogContent has no DialogTitle
Keyboard
| Escape | Close the dialog. |
| TabShift+Tab | Cycle focus within the dialog (focus is trapped). |
- aria-modal; labelled by DialogTitle and (if present) described by DialogDescription.
- Focus moves into the dialog on open and returns to the trigger on close.