Skip to content

Defining Fields

Fields can be defined in two modes: Unified Properties (all props grouped per field) or Separated Properties (props split across parallel objects). Both modes support flat and nested fields.


Quick Decision Guide

Use CaseRecommended Mode
Simple forms, few fields, quick prototypingUnified — all props in one place
Complex forms, many fields, reusable configsSeparated — props decoupled from structure
Server-provided values (e.g. DB query)Separated — pass values directly
Dynamic arrays (add/remove fields)Separated — struct array + separated props
Deeply nested objectsSeparated — dot notation is clearer
TypeScript autocomplete on field propsUnified with Record<string, FieldDefinition>

Mode Comparison

AspectUnifiedSeparated
Structure{ fields: { username: { label, value, ... } } }{ fields: ['username'], values: { ... }, labels: { ... } }
ProsCompact, self-contained, TS-friendlyFlexible, decoupled, easy to dynamically generate
ConsVerbose for large forms, harder to build dynamicallyMore boilerplate for tiny forms
When to useSmall forms, prototyping, TypeScriptLarge forms, dynamic fields, server data
Nested fieldsfields: [{ name, fields: [...] }]Dot notation 'parent.child' + array notation 'arr[]'

Available Props Reference

Unified Mode

Each field is an object keyed by name. Props are grouped by category below.

Value & Display

PropTypeDescription
valueanyInitial value
defaultanyValue used on reset()
initialanyFallback for value when not set
labelstringHuman-readable field label
placeholderstringPlaceholder text for the input
typestringField type, default "text"
extraanyExtra metadata (useful for select options)

Input Behavior & Accessibility

PropTypeDescription
autoFocusbooleanAuto-focus this field on init
inputModestringMobile keyboard mode: none, text, decimal, numeric, tel, search, email, url
autoCompletestringHTML autocomplete attribute
disabledbooleanDisabled state
deletedbooleanSoft-deleted state (needs softDelete option)
nullablebooleanAllow null field values
refanyReact ref

Validation

PropTypeDescription
rulesstringDVR validation rules (e.g. `'required
validatorsarrayVJF validation functions
validatedWithstringValidate a different field prop instead of value
relatedstring[]Other field paths to validate together

Converters & Computed

PropTypeDescription
inputfunctionInput converter: user value => stored value
outputfunctionOutput converter: stored value => output value
converterfunctionAlias for both input and output
convertersfunction[]Array of converter functions
computedfunctionDynamic value: ({ form, field }) => value

Events & MobX

PropTypeDescription
hooksobjectEvent hook functions
handlersobjectEvent handler functions
observersarrayMobX observers on field props
interceptorsarrayMobX interceptors on field props

Nested & Advanced

PropTypeDescription
fieldsarrayNested sub-field definitions
optionsobjectPer-field options (same keys as Form Options)
bindingsstringName of the binding template/rewriter
classanyCustom Field class (must extend Form.Field)
namestringOverrides the object key as field name

Separated Mode

Props are split across parallel objects keyed by field name or dot/array path:

Object KeyTypeDescription
fieldsstring[]Array of field paths (dot + [] notation)
structstring[]Alias for fields in separated mode
valuesobjectInitial values
initialsobjectFallback values when value is not set
defaultsobjectValues used on reset()
labelsobjectField labels
placeholdersobjectPlaceholder text
typesobjectField types
autoFocusobjectAuto-focus flags
inputModeobjectMobile keyboard modes
autoCompleteobjectAutocomplete attributes
rulesobjectDVR validation rules
validatorsobjectVJF validation functions
validatedWithobjectValidation prop overrides
relatedobjectRelated field paths
disabledobjectDisabled states
deletedobjectSoft-deleted states
nullableobjectNullable flags
optionsobjectPer-field options
bindingsobjectBinding keys
extraobjectExtra metadata
computedobjectComputed value functions
convertersobjectConverter functions
inputobjectInput converter functions
outputobjectOutput converter functions
hooksobjectEvent hook functions
handlersobjectEvent handler functions
observersobjectMobX observers
interceptorsobjectMobX interceptors
classesobjectCustom Field classes
refsobjectReact refs

Flat Fields

Flat: Unified Mode

javascript
import { Form } from 'mobx-react-form';

const fields = {
  username: {
    label: 'Username',
    value: 'SteveJobs',
    placeholder: 'Enter username',
    rules: 'required|string|between:5,15',
  },
  email: {
    label: 'Email',
    value: 's.jobs@apple.com',
    rules: 'required|email',
  },
};

const form = new Form({ fields });

💡 TypeScript: Annotate with Record<string, FieldDefinition> for autocomplete:

typescript
import { Form, FieldDefinition } from 'mobx-react-form';

const fields: Record<string, FieldDefinition> = {
  username: { label: 'Username', value: 'SteveJobs' },
};

const form = new Form(fields);

Shorthand (array of objects):

javascript
const fields = [
  { name: 'username', label: 'Username', value: 'SteveJobs' },
  { name: 'email', label: 'Email', value: 's.jobs@apple.com' },
];

new Form({ fields });

The name property is required when using array syntax.

Flat: Separated Mode

Define field names as an array, then provide props in parallel objects:

javascript
const fields = ['username', 'email', 'password'];

const values = {
  username: 'SteveJobs',
  email: 's.jobs@apple.com',
};

const labels = {
  username: 'Username',
  email: 'Email',
  password: 'Password',
};

const rules = {
  username: 'required|string|between:5,15',
  email: 'required|email',
  password: 'required|string|between:5,25',
};

new Form({ fields, values, labels, rules });

Auto-create: Providing only values (without fields array) will implicitly create the fields:

javascript
new Form({ values: { username: 'SteveJobs' } }); // field 'username' auto-created

Validation in Separated Mode

javascript
// DVR rules (declarative)
const rules = {
  email: 'required|email',
  password: 'required|string|min:6',
};

// VJF functions (vanilla)
const validators = {
  email: isEmail,
  emailConfirm: [isEmail, shouldBeEqualTo('email')],
};

new Form({ fields, rules, validators });

Nested Fields

Nested fields group fields into objects and arrays. The syntax differs between modes.

Nested: Unified Mode

Plain nested objects (single instance):

javascript
const fields = [{
  name: 'address',
  label: 'Address',
  fields: [{
    name: 'street',
    label: 'Street',
    value: 'Broadway',
    default: '5th Avenue',
  }, {
    name: 'city',
    label: 'City',
    value: 'New York',
  }],
}];

new Form({ fields });

The name property is required for each field when using array syntax.

Arrays of nested fields (multiple instances):

javascript
const fields = [{
  name: 'members',
  label: 'Team Members',
  fields: [{
    name: 'firstname',
    label: 'First Name',
  }, {
    name: 'lastname',
    label: 'Last Name',
  }],
}];

new Form({ fields });

// Add a new member dynamically:
form.$('members').add({ firstname: 'John', lastname: 'Doe' });

// Simple array (no nested fields):
form.$('hobbies').add({ value: 'Tennis' });

Related fields across nesting levels:

javascript
const fields = [{
  name: 'user',
  fields: [{
    name: 'email',
    label: 'Email',
    validators: [isEmail],
    related: ['user.emailConfirm'], // reference by full path
  }, {
    name: 'emailConfirm',
    label: 'Confirm Email',
    validators: [isEmail, shouldBeEqualTo('email')],
  }],
}];

Nested: Separated Mode

Use dot notation for nested objects and [] notation for arrays:

javascript
const fields = [
  'club.name',
  'club.city',
  'members',
  'members[].firstname',
  'members[].lastname',
  'members[].hobbies',
  'members[].hobbies[]',
];

const values = {
  club: { name: 'Jazz Club', city: 'New York' },
  members: [{
    firstname: 'Clint',
    lastname: 'Eastwood',
    hobbies: ['Soccer', 'Baseball'],
  }, {
    firstname: 'Charlie',
    lastname: 'Chaplin',
    hobbies: ['Golf', 'Basket'],
  }],
};

const labels = {
  'club': 'Club',
  'club.name': 'Club Name',
  'club.city': 'Club City',
  'members': 'All Members',
  'members[].firstname': 'Member First Name',
  'members[].lastname': 'Member Last Name',
};

const rules = {
  'club.name': 'required|min:3',
  'club.city': 'required|min:3',
  'members[].firstname': 'required|min:3',
  'members[].lastname': 'required|min:3',
};

new Form({ fields, values, labels, rules });

Dot notation — keys reference field paths (e.g., 'club.name'). Array notationmembers[] applies to every element in the array. Deeply nested arrayshobbies[] inside members[] creates arrays of arrays.

Alternative syntax — property values as nested objects:

javascript
const labels = {
  club: {
    name: 'Club Name',
    city: 'Club City',
  },
  members: [{
    firstname: 'First Name',
    lastname: 'Last Name',
  }],
};

Mixed Mode (Unified + Separated)

You can combine both modes. Props defined in the fields object (unified) take precedence over separated props:

javascript
new Form({
  // Separated mode props (applied first)
  values: { username: 'SteveJobs' },
  labels: { username: 'Username' },
  rules:  { username: 'required|string' },

  // Unified mode override (takes precedence)
  fields: {
    username: {
      type: 'email',     // overrides default 'text'
      disabled: false,
    },
  },
});

When fallback option is true (default), field props not found in separated mode fall back to the unified definition.


Common Patterns

Fields with Default & Initial Values

javascript
const fields = {
  newsletter: {
    label: 'Subscribe',
    type: 'checkbox',
    value: true,    // initial value on mount
    default: false, // value after reset
  },
};

Fields with Per-Field Options

Override form-level options on individual fields:

javascript
const fields = {
  email: {
    label: 'Email',
    rules: 'required|email',
    options: {
      validateOnChange: true,       // validate on every keystroke
      validateOnBlur: false,        // skip blur validation
      showErrorsOnChange: true,     // show errors immediately
    },
  },
};

Fields with Custom Bindings

javascript
const fields = {
  username: {
    label: 'Username',
    bindings: 'MaterialTextField', // use a registered binding
  },
};

Fields with Auto-Focus

javascript
const fields = {
  email: {
    label: 'Email',
    autoFocus: true, // focused on form mount
  },
};

Fields with Password Confirmation

Use the related prop to re-validate the confirmation field when the password changes:

javascript
const fields = {
  password: {
    label: 'Password',
    type: 'password',
    rules: 'required|string|min:8',
    related: ['passwordConfirm'],   // re-validate confirm on password change
  },
  passwordConfirm: {
    label: 'Confirm Password',
    type: 'password',
    validators: [shouldBeEqualTo('password')],
  },
};

The related property on password tells the form to re-validate passwordConfirm every time the password value changes. This ensures the confirmation error message appears/disappears in real-time as the user types.

The shouldBeEqualTo('password') validator (custom VJF function) compares the confirmation value against the password field.

javascript
// Custom validator function
function shouldBeEqualTo(target) {
  return ({ field, form }) => {
    const match = form.$(target).value === field.value;
    return [match, `${field.label} should be equal to ${form.$(target).label}`];
  };
}

Accessing Fields at Runtime

javascript
form.$('username');              // flat field
form.$('address.city');          // nested field (dot notation)
form.$('members[0].firstname');  // array element by index
form.$('members').$(0).$('firstname'); // chained selector

💡 TypeScript: The $() method provides type-safe autocomplete for both top-level and nested paths when using Form<F>:

typescript
interface MyForm {
  members: Array<{ firstname: string; lastname: string }>;
}
const form = new Form<MyForm>({ fields });
form.$('members[0].firstname').value; // typed as string

See the TypeScript Guide for details.

Released under the MIT License.