setupForm

BlueForm does not expose a single global <Form /> component. Instead, you create a form system bounded to a base configuration through a setup step. This is the entry point of any BlueForm-powered app.

import { setupForm, defineMapping } from 'react-headless-form'
import InputField from './InputField'

export const [Form, defineConfig, Section] = setupForm({
  renderRoot: ({ children, onSubmit }) => (
    <form onSubmit={onSubmit}>{children}</form>
  ),
  fieldMapping: defineMapping({
    text: InputField,
  }),
})

setupForm returns a tuple of three values — destructure and name them as you see fit:

ValueDescription
FormThe form component, bound to your field mapping
defineConfigHelper for typing nested config fragments
SectionComponent for authoring typed config fragments inside section components
Tip

Form, defineConfig, and Section are the names used throughout this documentation. Since they come from destructuring an array, you can rename them to anything that fits your codebase.

Options

OptionDescription
renderRootHow the root form element is rendered — see below
fieldMappingMaps field type names to React components
i18nConfigTranslation behavior for labels and validation messages
formOptionsDefault options for RHF's useForm, applied to every form instance

renderRoot

Controls how the root form element is rendered. This keeps BlueForm layout-agnostic and platform-independent.

renderRoot receives children, onSubmit, and a curated set of form methods spread directly onto the args — formState, setValue, reset, trigger, control, and others. See the full list below.

ArgDescription
childrenRendered form content
onSubmitSubmit handler, bind to form element's onSubmit
formStateRHFisDirty, isValid, isSubmitting, errors, ...
controlRHF — for DevTools and third-party integrations
setValueRHF
getValuesRHF
getFieldStateRHF
resetRHF
resetFieldRHF
setErrorRHF
clearErrorsRHF
triggerRHF
setFocusRHF
renderRoot: ({ children, onSubmit, control, formState }) => (
  <>
    <form onSubmit={onSubmit}>
      {children}
      <button
        type="submit"
        disabled={!formState.isDirty || formState.isSubmitting}
      >
        {formState.isSubmitting ? "Submitting..." : "Submit"}
      </button>
    </form>
    <DevTool control={control} />
  </>
)

renderRoot can be overridden per <Form /> instance.

Important

If neither setupForm nor the <Form /> instance provides renderRoot, an error will be thrown at run time.

fieldMapping

Maps field type names to React components. Each entry tells the engine which component to render when a field config declares a given type.

fieldMapping: defineMapping({
  text: InputField,
  select: SelectField,
  datepicker: DatePickerField,
})

The mapping is enforced at compile time — declaring an unknown type in form config produces a TypeScript error. fieldMapping cannot be overridden per <Form /> instance — it is fixed at setup time to preserve type safety.

For how to author field components, see Field authoring.

i18nConfig

Provides translation support for labels, descriptions, and validation messages. Optional — if omitted, all text values are treated as plain strings.

i18nConfig: {
  t: (key, params) => i18next.t(key, params),
  validationTranslation: {
    required: 'validation.required',
    minLength: 'validation.minLength',
  },
}

validationTranslation maps RHF rule names to translation keys so validation messages are translated automatically — without putting translation keys into field config. For a full guide, see Internationalization.

formOptions

Default options passed to RHF's useForm for every form instance created from this setup. Accepts a subset of useForm options — behavior-level settings that make sense app-wide:

formOptions: {
  mode: 'onChange',
  shouldFocusError: false,
}

Per-form concerns — resolver, defaultValues, context — belong on the <Form /> instance instead. Per-form formOptions merges over these defaults, so individual forms can override specific keys without losing the rest:

// setup-level: mode = "onChange"
const [Form] = setupForm({
  formOptions: { mode: 'onChange' },
  ...
})

// this form overrides shouldFocusError, but still uses mode = "onChange"
<Form formOptions={{ shouldFocusError: true }} ... />