Skip to main content

createAutoForm()

createAutoForm(config) creates a pre-configured <AutoForm> component that bakes in your design system's components, layout slots, class names, and other defaults. Consumer code only needs to supply form, onSubmit, and any per-instance overrides.

import { createAutoForm } from '@uniform-ts/core'

export const MyForm = createAutoForm({
components: myRegistry,
layout: { formWrapper: MyCard, submitButton: MyPrimaryButton },
classNames: { form: 'space-y-6', fieldWrapper: 'flex flex-col gap-1' },
})

Then in any feature:

<MyForm form={userForm} onSubmit={handleSubmit} />

AutoFormConfig options

OptionTypeDescription
componentsComponentRegistryBase component registry (merged with defaultRegistry)
fieldWrapperReact.ComponentType<FieldWrapperProps>Default field wrapper component
layoutLayoutSlotsDefault layout slots (formWrapper, sectionWrapper, submitButton, arrayRowLayout, loadingFallback). Use null for omittable slots (submit/array buttons).
classNamesFormClassNamesDefault CSS class names
disabledbooleanWhen true, all fields in every form instance are disabled by default
messagesValidationMessagesDefault validation messages
coercionsCoercionMapDefault per-field coercions
labelsFormLabelsDefault UI labels (submit, array buttons, aria labels). Import a locale pack from @uniform-ts/core/locales/{en,he,es} to set all strings at once

Merge behaviour

Per-instance props are shallow-merged on top of config defaults. This means any prop you pass at the call site wins. components registries are deep-merged using mergeRegistries, so you can override one field type without replacing the whole registry.

Live Example

A design-system form where every text input has a blue left border and every field wrapper has a bottom line:

Live Editor
const inputStyle = {
  borderLeft: '3px solid #4F46E5',
  paddingLeft: 8,
  borderTop: 'none',
  borderRight: 'none',
  borderBottom: '1px solid var(--ifm-color-emphasis-300)',
  outline: 'none',
  width: '100%',
  fontSize: 14,
  padding: '6px 8px',
}

const wrapperStyle = {
  borderBottom: '1px solid var(--ifm-color-emphasis-200)',
  paddingBottom: 12,
  marginBottom: 12,
}

const CustomInput = ({ value, onChange, onBlur, ref, placeholder, error }) => (
  <div style={wrapperStyle}>
    <input
      ref={ref}
      value={String(value ?? '')}
      onChange={(e) => onChange(e.target.value)}
      onBlur={onBlur}
      style={inputStyle}
      placeholder={placeholder}
    />
    {error && (
      <span style={{ color: 'var(--ifm-color-danger)', fontSize: 12 }}>
        {error}
      </span>
    )}
  </div>
)

const BrandedForm = createAutoForm({
  components: { string: CustomInput },
  labels: { submit: 'Save Changes' },
})

const schema = z.object({
  username: z.string().min(3),
  bio: z.string().optional(),
})

const profileForm = createForm(schema)

function App() {
  const [saved, setSaved] = React.useState(false)
  return (
    <div style={{ fontFamily: 'system-ui', maxWidth: 380 }}>
      {saved && <p style={{ color: 'var(--ifm-color-success)' }}>Saved!</p>}
      <BrandedForm
        form={profileForm}
        fields={{
          username: { label: 'Username' },
          bio: { label: 'Short bio' },
        }}
        onSubmit={() => setSaved(true)}
      />
    </div>
  )
}

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