# Unity TanStack Form patterns

### Composed API (default)

Drop a single bound field component inside `<form.AppField>`. It bundles label, input, helper text, feedback, and a11y wiring.

```tsx
<form.AppField name="firstName">
  {field => (
    <field.TextField
      label="First name"
      helperText="As it appears on your ID"
      isRequired
    />
  )}
</form.AppField>

<form.AppField name="country">
  {field => (
    <field.SelectField
      label="Country"
      options={[
        { value: 'fr', label: 'France' },
        { value: 'es', label: 'Spain' },
      ]}
    />
  )}
</form.AppField>
```

### Atomic API (only when customizing layout/parts)

Reach for Atomic when you need to interleave custom content between the label and the input, or swap an input for a non-standard control. Wraps every part in `field.Field`.

```tsx
<form.AppField name="password">
  {field => (
    <field.Field>
      <field.FieldLabel isRequired>Password</field.FieldLabel>
      <field.FieldHelperText>Enter a strong password</field.FieldHelperText>
      <field.TextInput type="password" />
      <form.Subscribe selector={s => s.values.password}>
        {password => (
          <Text variant="bodySmallStrong">Length: {password.length}</Text>
        )}
      </form.Subscribe>
      <field.FieldFeedbackText />
    </field.Field>
  )}
</form.AppField>
```

### Validation timing

`validators.onBlur` is the default; use `onChange` only for fields that need live feedback (password strength meter, search-as-you-type). `fieldRevalidateLogic` gives "blur until first error, then change" UX without polluting form-level validators.

```tsx
import { fieldRevalidateLogic, useTanstackUnityForm } from '@payfit/unity-components'

const form = useTanstackUnityForm({
  defaultValues: { email: '', password: '' },
  validators: { onBlur: z.object({ email: z.email() }) },
  validationLogic: fieldRevalidateLogic({
    whenPristine: 'blur',
    whenDirty: 'change',
    fields: ['password'],
  }),
})

<form.AppField
  name="password"
  validators={{
    onDynamic: ({ value }) =>
      value.length < 8 ? 'Password too short' : undefined,
  }}
>
  {field => <field.PasswordField label="Password" />}
</form.AppField>
```

For fields listed in `fieldRevalidateLogic.fields`, use
`onDynamic`/`onDynamicAsync` as the sole validator and omit those fields from
the form-level schema; combining both leaves stale errors.

### Optimal subscription with form.Subscribe + selector

Pass a `selector` to `form.Subscribe` so it observes only the state needed by
its children. A bare subscription re-renders on every form update.

```tsx
<form.Subscribe selector={s => s.values.password}>
  {password => <Text>Length: {password.length}</Text>}
</form.Subscribe>

<form.Subscribe selector={s => [s.canSubmit, s.isSubmitting] as const}>
  {([canSubmit, isSubmitting]) => (
    <Button type="submit" isDisabled={!canSubmit} isLoading={isSubmitting}>
      Submit
    </Button>
  )}
</form.Subscribe>
```
