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.
  • form.SubmitButton subscribes to canSubmit and disables itself while the form is invalid or submitting.

Usage

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

import { 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'), })

Errors render inline per field: the bound field components translate and display their own errors automatically. For a custom field, use FieldError with the field’s raw errors.

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