Compose: Dialog, DialogTrigger, DialogContent, DialogTitle, DialogDescription, DialogClose, DialogFooter. Triggers and closes use asChild by default — wrap any element (Button, Link, custom Pressable) and it becomes the activator.
Cross-platform: uses RN <Modal> as the visibility primitive on every target. On web, additional effects layer on: focus trap, body scroll lock, Escape-to-close, click-outside-to-close, and focus restored to the trigger on close.
Accessibility: role="dialog" with aria-modal, aria-labelledby on the title, aria-describedby on the description.
open (controlled) + onOpenChange is the parent-driven shape; pass
defaultOpen instead for uncontrolled mode when the Dialog should
manage its own open state. Mixing the two prefers controlled and
ignores defaultOpen. The Trigger and Close subcomponents flip the
state for you, so most use sites don't need either prop.
// Uncontrolled — Trigger / Close manage open state.<Dialog> <Dialog.Trigger><Button>Open</Button></Dialog.Trigger> <Dialog.Content>...</Dialog.Content></Dialog>// Controlled — parent owns the state, useful for closing the dialog// after an async save completes.const [open, setOpen] = useState(false);<Dialog open={open} onOpenChange={setOpen}> <Dialog.Trigger><Button>Open</Button></Dialog.Trigger> <Dialog.Content>...</Dialog.Content></Dialog>