Skip to main content

Field Overrides

The fields prop on <AutoForm> accepts a map of field paths to FieldOverride objects. This lets you customise presentation and behaviour of any field without touching the Zod schema.

FieldOverride reference

PropertyTypeDescription
labelstringOverride auto-derived label
descriptionstringHelp text shown below the field
placeholderstringPlaceholder for text inputs
ordernumberRender order (lower = first)
spannumberColumn span (1–12) in a 12-column grid
sectionstringGroup field into a named section
hiddenbooleanHard-hide (never renders, never validates)
disabledbooleanDisable just this field
componentstring | React.ComponentType<FieldProps>Registry key ("textarea", "rating", …) or an inline React component
optionsArray<{ label: string; value: string | number }>Override select options for enum fields
condition(values) => booleanShow (true) / hide (false) based on current form values
onChange(value, form: FormMethods) => void | PromiseCalled when this field changes; access all form methods via form
movableboolean(array fields) Enable move-up/move-down row controls
duplicableboolean(array fields) Enable a duplicate row button
collapsibleboolean(array fields) Enable collapse/expand per row

Inline condition

You can show/hide a field inline without calling form.setCondition():

fields={{
vatNumber: {
condition: (values) => values.isBusinessAccount === true,
},
}}

For more complex cases involving multiple fields, use createForm() with setCondition.

Inline onChange

React to a field change without createForm().setOnChange():

fields={{
country: {
onChange: async (value, form) => {
const regions = await fetchRegions(value)
form.setValue('region', regions[0])
},
},
}}

Inline component

component accepts either a registry key string or a React component directly:

fields={{
bio: { component: 'textarea' }, // registry key
rating: { component: StarRatingComponent }, // inline component
}}

Nested fields

Use dot notation to override deeply nested fields:

fields={{
'address.street': { label: 'Street address', span: 12 },
'address.city': { label: 'City', span: 7 },
'address.zip': { label: 'ZIP code', span: 5 },
}}

Custom options

For z.enum fields you can override the displayed labels without changing the schema:

const schema = z.object({ plan: z.enum(['free', 'pro', 'enterprise']) })

fields={{
plan: {
options: [
{ value: 'free', label: 'Free — $0/mo' },
{ value: 'pro', label: 'Pro — $12/mo' },
{ value: 'enterprise', label: 'Enterprise — contact us' },
]
}
}}

Live Example

Live Editor
const schema = z.object({
  title: z.string().min(1, 'Required'),
  firstName: z.string().min(1, 'Required'),
  lastName: z.string().min(1, 'Required'),
  email: z.string().email(),
  bio: z.string().optional(),
  plan: z.enum(['free', 'pro', 'enterprise']),
  agreeToTerms: z.boolean(),
})

const signupForm = createForm(schema)

function App() {
  const [result, setResult] = React.useState(null)
  return (
    <div style={{ fontFamily: 'system-ui', maxWidth: 500 }}>
      <AutoForm
        form={signupForm}
        defaultValues={{ plan: 'free', agreeToTerms: false }}
        fields={{
          title: {
            label: 'Title',
            order: 1,
            span: 3,
            options: [
              { value: 'Mr', label: 'Mr' },
              { value: 'Ms', label: 'Ms' },
              { value: 'Dr', label: 'Dr' },
            ],
            component: 'enum',
          },
          firstName: { label: 'First name', order: 2, span: 5 },
          lastName: { label: 'Last name', order: 3, span: 4 },
          email: {
            label: 'Email address',
            order: 4,
            description: 'Used for login',
            span: 12,
          },
          plan: {
            order: 5,
            options: [
              { value: 'free', label: 'Free — $0/mo' },
              { value: 'pro', label: 'Pro — $12/mo' },
              { value: 'enterprise', label: 'Enterprise — contact us' },
            ],
          },
          bio: {
            label: 'Short bio',
            order: 6,
            description: 'Optional, shown on your profile',
          },
          agreeToTerms: { label: 'I agree to the Terms of Service', order: 7 },
        }}
        onSubmit={(v) => setResult(v)}
      />
      {result && (
        <pre
          style={{
            marginTop: '1rem',
            background: 'var(--ifm-color-emphasis-200)',
            padding: '1rem',
            borderRadius: 6,
          }}
        >
          {JSON.stringify(result, null, 2)}
        </pre>
      )}
    </div>
  )
}

render(<App />)
Result
Loading...