# Forms Guide ## Table of Contents 1. [Architecture](#architecture) 2. [Field Types](#field-types) 3. [Usage Patterns](#usage-patterns) 4. [Validation Strategies](#validation-strategies) 5. [Sheet/Dialog Forms](#sheetdialog-forms) 6. [Multi-Step Forms](#multi-step-forms) 7. [Advanced Patterns](#advanced-patterns) --- ## Architecture The form system is built on **TanStack Form + Zod** with a composable field layer. **Key files:** - `src/components/ui/tanstack-form.tsx` — exports `useAppForm`, `useFormFields()`, composed fields - `src/components/ui/form-context.tsx` — contexts, `createFormField`, structural components - `src/components/forms/fields/*.tsx` — 8 field type implementations **Key exports:** ```tsx import { useAppForm, useFormFields } from '@/components/ui/tanstack-form'; ``` - `useAppForm(config)` — creates a form instance with `defaultValues`, `validators`, `onSubmit` - `useFormFields()` — returns all 8 typed field components with name autocomplete from `T` - `form.AppForm` — context provider wrapper - `form.Form` — `
` element that handles submit - `form.SubmitButton` — auto-disabled when form is invalid or submitting - `form.AppField` — low-level render prop for custom fields --- ## Field Types All fields accept: `name`, `label`, `description`, `required`, `disabled`, `validators`, `listeners`, `className`. | Component | Props | Notes | | --------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------- | | `FormTextField` | `type` (text/email/number/password/tel/url), `placeholder`, `min`, `max`, `step`, `maxLength` | For numbers use `type='number'` | | `FormTextareaField` | `placeholder`, `rows`, `maxLength` | Multiline text | | `FormSelectField` | `options: {value, label}[]`, `placeholder` | Single select dropdown | | `FormCheckboxField` | `options?: {value, label}[]` | Single checkbox or multi-checkbox group | | `FormSwitchField` | — | Toggle switch | | `FormRadioGroupField` | `options: {value, label}[]`, `orientation` | Radio button group | | `FormSliderField` | `min`, `max`, `step` | Range slider | | `FormFileUploadField` | `maxSize`, `maxFiles`, `accept` | Drag-and-drop with preview | --- ## Usage Patterns ### Pattern 1: `useFormFields()` (Recommended) Type-safe field components with name autocomplete: ```tsx const { FormTextField, FormSelectField } = useFormFields(); ``` ### Pattern 2: `form.AppField` render prop Full control for custom field rendering: ```tsx {(field) => ( )} ``` ### Pattern 3: Direct import (no type safety) For quick prototyping: ```tsx import { FormTextField } from '@/components/ui/tanstack-form'; ; ``` --- ## Validation Strategies ### Field-level (recommended for UX) ```tsx ``` ### Form-level (catch-all on submit) ```tsx const form = useAppForm({ validators: { onSubmit: orderSchema }, // Validates entire form on submit onSubmit: async ({ value }) => { /* ... */ } }); ``` ### Async validation (server-side checks) ```tsx { const exists = await checkUsername(value); return exists ? 'Username taken' : undefined; } }} asyncDebounceMs={500} /> ``` ### Linked field validation For dependent fields (e.g., confirm password): ```tsx { const password = fieldApi.form.getFieldValue('password'); return value !== password ? 'Passwords must match' : undefined; } }} /> ``` --- ## Sheet/Dialog Forms The key pattern for forms inside sheets or dialogs: give the `` an `id`, and use that `id` on the submit button's `form` attribute. This allows the submit button to live outside the form element (e.g., in `SheetFooter`). ```tsx {/* fields */} ; { /* In SheetFooter — button is outside the but still submits it */ } ; ``` On success, call `onOpenChange(false)` to close the sheet and `form.reset()` for create forms. --- ## Multi-Step Forms Use `withFieldGroup` + `useAppForm` with `StepButton`: ```tsx // Define field groups for each step const Step1 = withFieldGroup({ fields: ['name', 'email'], render: ({ form }) => { const { FormTextField } = useFormFields(); return ( <> ); } }); const Step2 = withFieldGroup({ fields: ['address', 'city'], render: ({ form }) => { const { FormTextField } = useFormFields(); return ( <> ); } }); ``` Use the `useStepper` hook from `src/hooks/use-stepper.tsx` to manage step state. --- ## Advanced Patterns ### Nested objects (dot notation) ```tsx ``` ### Dynamic array rows ```tsx {(field) => ( <> {field.state.value.map((_, i) => ( {(subField) => } ))} )} ``` ### Side effects with listeners ```tsx { // Reset city when country changes form.setFieldValue('city', ''); } }} /> ``` ### Custom field with `form.AppField` For fields not covered by the built-in 8 types: ```tsx {(field) => ( field.handleChange(e.target.value)} /> )} ``` ### Form-level errors Display errors that apply to the whole form (e.g., server errors): ```tsx import { FormErrors } from '@/components/ui/form-context'; {/* Renders form-level validation errors */} {/* fields... */} ; ```