Form Dialog
Compose accessible form dialogs with caller-owned validation and submission.
Import the form primitives from @tulip-systems/ui/form-dialog and the header,
title, description, and footer from @tulip-systems/ui/dialog.
<FormDialog open={open} onOpenChange={setOpen} pending={save.isPending}>
<FormDialogTrigger asChild>
<Button>Add contact</Button>
</FormDialogTrigger>
<FormDialogContent>
<FormDialogForm onSubmit={form.handleSubmit((values) => save.mutate(values))}>
<DialogHeader>
<DialogTitle>Add contact</DialogTitle>
<DialogDescription>Enter the contact details.</DialogDescription>
</DialogHeader>
<FormDialogFields>{/*
Your form fields
*/}</FormDialogFields>
<DialogFooter>
<FormDialogCancel>Cancel</FormDialogCancel>
<FormDialogSubmit>Save</FormDialogSubmit>
</DialogFooter>
</FormDialogForm>
</FormDialogContent>
</FormDialog>The root supports open, defaultOpen, onOpenChange, and pending (default
false). Use controlled opening when a successful save should close the dialog.
The preview uses native inputs and FormData; the repeater playground
uses React Hook Form and TanStack Query. Neither library is required by FormDialog.
Submission and dismissal
FormDialogForm renders a native form and accepts standard form attributes.
Provide your own submit handler, validation, and error UI. Plain event handlers
must call preventDefault() to avoid browser navigation when saving with JavaScript.
Submission never automatically closes or resets the form.
While pending, submit and cancel buttons are disabled, the close icon is hidden,
and Escape and outside interactions cannot dismiss the dialog. The form prevents
additional submit events. A controlled parent can still close the dialog after a
successful save. Set pending promptly when starting your request; the component
does not track promises or replace server-side duplicate-request protection.
Cancel only closes. Reset form values and mutation errors in onOpenChange if
needed. A failed save can leave the dialog open with its values intact for retry.
Inputs remain caller-owned; pass disabled to fields if editing during a save should
be prevented.
Composition
FormDialogTrigger supports asChild. Content forwards the underlying dialog's
props; form and fields accept native props and className. Cancel and submit accept
button styling and disabled props. Submit also supports the native form attribute
for placement outside the form. Use one form per dialog and avoid nested forms.