Form

Validate, create and edit a group of fields with one Zod schema.

Example

Profile
Installation
bunx --bun shadcn@latest add @fieldkit/form

Needs the @fieldkit registry. Add it once

Usage
Terminal
import { Form } from "@/components/form/form"import { FormEmail } from "@/components/form/form-email"import { FormActions, FormLayout } from "@/components/form/form-layout"import { FormRow } from "@/components/form/form-row"import { FormSubmit } from "@/components/form/form-submit"import { FormText } from "@/components/form/form-text"import { FieldGroup } from "@/components/ui/field"
<FormLayout title="Profile">  <Form schema={schema} defaultValues={defaults} onSubmit={(values) => save(values)}>    <FieldGroup>      <FormRow>        <FormText name="firstName" />        <FormText name="lastName" />      </FormRow>      <FormEmail name="email" />      <FormActions>        <FormSubmit>Save profile</FormSubmit>      </FormActions>    </FieldGroup>  </Form></FormLayout>
// Edit an existing record<Form  key={member.id}  mode="edit"  schema={memberSchema}  defaultValues={toFormValues(member)}  onSubmit={(values, { changed }) => updateMember(member.id, changed)}>  <FieldGroup>    <FormMessage />    ...    <FormActions>      <FormReset />      <FormSubmit>Save changes</FormSubmit>    </FormActions>    <FormLeaveGuard />  </FieldGroup></Form>
// Show server errors on fieldsonSubmit={async (values) => {  const result = await createMember(values)  if (result.emailTaken) {    throw new FormSubmitError({      message: "Fix the highlighted field and try again.",      fields: { email: "This email already has an account" },    })  }}}
API reference

Form

schema (required)z.ZodTypeZod v4 schema. Inputs read their rules and the required asterisk from it.
defaultValues (required)z.input<TSchema>Empty values to create, the record's values to edit.
onSubmit(values, { changed, payload, form }) => void | Promise<void>Gets the parsed output, the keys that changed, and payload to send. Throw FormSubmitError to show server errors. Optional when storage has an adapter with submit.
storage{ formId, folder?, maxImageMb?, imageTypes?, maxFileMb?, fileTypes?, adapter? }Where this form keeps rich text images and files, uploaded files and, without onSubmit, its submissions. adapter falls back to FormStorageProvider.
recordIdstringSent to storage.adapter.submit when editing a record.
mode"create" | "edit" = "create"In edit mode FormSubmit waits for a change.
validateOn"onChange" | "onBlur" | "onSubmit" = formConfig.validateOnWhen fields validate before submit.
editMethod"put" | "patch" = formConfig.editMethodIn edit mode, payload holds every value for put, only changed values for patch.
messagesPartial<SubmitMessages> = formConfig.submit.messagesToast titles for this form. success: false hides the success toast.

FormSubmitError

messagestringShown by FormMessage above the fields and in the error toast.
fieldsRecord<string, string>Errors shown on each named field until it changes.

FormField and FormFieldFrame

name (required)stringKey in the schema.
requiredbooleanOverrides the asterisk read from the schema.
validatorsFieldValidatorsExtra field checks, such as onBlurAsync.
Notes
  • FormSubmit shows a spinner until onSubmit resolves. Return its promise so the form waits for the save.
  • After a successful submit the form resets to what was saved, so edit mode shows no changes again.
  • Change key to load a different record into the same form.
  • Any other thrown error shows its message in FormMessage and an error toast.
  • Mount Toaster from @/components/ui/toast once in the root layout. Toasts do not render without it.
  • The asterisk shows when the schema rejects an empty value.

Search docs

Find a component, input or example