Skip to content

Dialog

A modal surface that interrupts the flow for a focused task or message.

Stablev0.4.2added in v0.1.0@garn/ui/dialog
On this page

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.

terminal
npx garn-ui add dialog
First time? Set up garn in your project
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.

DialogRequired

Root — 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

SelectorState
[data-state=open]open
[data-state=closed]closed
PropertyTokenTier
padding--garn-pad-surfacesemantic · air
gap--garn-gap-stacksemantic · air
closeSize--garn-control-h-xssemantic · air
motion--garn-motion-fastsemantic

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

Do
  • 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
  • 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

warn

Every DialogContent must contain a DialogTitle (wrap it in sr-only to hide it visually).

when DialogContent has no DialogTitle

Keyboard

EscapeClose the dialog.
TabShift+TabCycle focus within the dialog (focus is trapped).
A1.3.1Info and RelationshipsA2.1.1KeyboardA2.4.3Focus OrderA4.1.2Name, Role, Value
  • 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.