Skip to Content

Form

A composition-first form for building typed, validated forms from reusable pieces. The field components are pre-bound to Apollo’s Field primitives, so a form is assembled declaratively instead of wiring inputs by hand.

What it is built on

The Form component is a thin Apollo layer over two libraries, so you get their behavior without the boilerplate:

  • TanStack Form handles state, subscriptions, and submission. useAppForm is created once via createFormHook and wired to the Apollo field components, so each field is rendered with form.AppField.
  • Zod describes the shape and rules. Zod v4 implements the Standard Schema  spec, so a schema passes directly into TanStack Form validators. No resolver or adapter is required.

Installation

npx shadcn@latest add @uipath/form

How it works

  • form.AppField provides the field context. Its render prop exposes the bound components (field.TextField, field.SelectField, etc).
  • form.AppForm provides the form context to form-level components such as SubmitButton and FormErrorSummary.
  • form.SubmitButton subscribes to canSubmit and disables itself while the form is invalid or submitting.

We'll never share your email.

Maximum 160 characters.

Receive occasional product news.

Usage

Validation is defined once as a whole-form Zod schema passed to validators.onChange.

import * as z from 'zod' import { FieldGroup } from '@/components/ui/field' import { useAppForm } from '@/components/ui/form' const schema = z.object({ fullName: z.string().min(2, 'Name must be at least 2 characters.'), email: z.email('Enter a valid email address.'), acceptTerms: z.boolean().refine((value) => value, { message: 'You must accept the terms.', }), }) function SignUpForm() { const form = useAppForm({ defaultValues: { fullName: '', email: '', acceptTerms: false }, validators: { onChange: schema }, onSubmit: ({ value }) => console.log(value), }) return ( <form onSubmit={(event) => { event.preventDefault() void form.handleSubmit() }} > <form.AppForm> <FieldGroup> <form.AppField name="fullName"> {(field) => <field.TextField label="Full name" />} </form.AppField> <form.AppField name="email"> {(field) => <field.TextField type="email" label="Email" />} </form.AppField> <form.AppField name="acceptTerms"> {(field) => <field.CheckboxField label="I accept the terms" />} </form.AppField> <form.SubmitButton>Create account</form.SubmitButton> </FieldGroup> </form.AppForm> </form> ) }

Field components

ComponentValue typeNotes
TextFieldstringWraps Input, forwards native input props.
TextareaFieldstringWraps Textarea.
SelectFieldstringTakes an options array of { label, value }.
RadioGroupFieldstringTakes an options array of { label, value }.
CheckboxFieldbooleanHorizontal layout with label and description.
SwitchFieldbooleanHorizontal layout with label and description.

Translatable validation errors

Error messages are resolved through react-i18next. Each message is passed to t(message, { defaultValue: message }), so a message that matches a translation key is localized, and a plain sentence still renders as-is. Use a translation key as the Zod message to make an error translatable.

const schema = z.object({ email: z.email('form_error_email_invalid'), })

Two components consume these messages:

  • Per field: the bound field components render their own translated errors automatically. For a custom field, use FieldError with the field’s raw errors.
  • Form level: FormErrorSummary lists every field’s errors in one translated, accessible block. Render it inside form.AppForm.
<form.AppForm> <form.FormErrorSummary title="Please fix the following:" /> {/* fields */} </form.AppForm>

Field-level validation

The whole-form schema is the recommended default, but individual fields can add their own validators. Field-level validators run alongside the form schema.

<form.AppField name="email" validators={{ onChange: z.email('Enter a valid email address.') }} > {(field) => <field.TextField type="email" label="Email" />} </form.AppField>
Last updated on