Defining Fields
Fields can be defined in two modes: Unified Properties (props grouped per field) or Separated Properties (props split across objects). Both modes support flat and nested fields.
Quick Decision Guide
| Use Case | Recommended Mode |
|---|---|
| Simple forms, few fields, quick prototyping | Unified — all props in one place |
| Complex forms, many fields, reusable configs | Separated — 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 objects | Separated — dot notation is clearer |
| TypeScript autocomplete on field props | Unified with Record<string, FieldDefinition> |
Mode Comparison
| Aspect | Unified | Separated |
|---|---|---|
| Structure | { fields: { username: { label, value, rules, ... } } } |
{ fields: ['username'], values: { ... }, labels: { ... }, rules: { ... } } |
| Pros | Compact, self-contained, TS-friendly | Flexible, decoupled, easy to dynamically generate |
| Cons | Verbose for large forms, harder to dynamically build | More boilerplate for tiny forms |
| When to use | Small forms, prototyping, TypeScript | Large forms, dynamic fields, server data |
| Nested fields | fields: [{ name, fields: [...] }] |
Dot notation 'parent.child' + array notation 'arr[]' |
Available Props Reference
Unified Mode
Each field is an object keyed by name. Available props per field:
| Prop | Type | Description | |
|---|---|---|---|
value |
any | Initial value | |
label |
string | Field label | |
placeholder |
string | Placeholder text | |
rules |
string | DVR validation rules (e.g. `'required | email'`) |
validators |
array | VJF validation functions | |
type |
string | Field type, default "text" |
|
disabled |
boolean | Disabled state | |
deleted |
boolean | Soft-deleted state (needs softDelete option) |
|
related |
string[] | Related field paths to validate together | |
default |
any | Default value (used on reset) |
|
initial |
any | Initial value (fallback for value) |
|
bindings |
string | Binding template/rewriter key | |
options |
object | Field-level options (same as Form Options) | |
extra |
any | Extra metadata (useful for select options) | |
hooks |
object | Event hooks (see Events) | |
handlers |
object | Event handlers (see Events) | |
input |
function | Input converter: value => stored |
|
output |
function | Output converter: stored => output |
|
converter |
function | Value converter (alias for input/output) | |
converters |
function[] | Array of converter functions | |
computed |
function | Computed value function ({ form, field }) => value |
|
validatedWith |
string | Field prop to validate instead of value |
|
observers |
array | MobX observers | |
interceptors |
array | MobX interceptors | |
autoFocus |
boolean | Auto-focus on init | |
inputMode |
string | Mobile keyboard mode | |
ref |
any | React ref | |
nullable |
boolean | Allow null values | |
autoComplete |
string | HTML autocomplete attribute | |
class |
any | Custom Field class | |
fields |
array | Nested sub-field definitions | |
name |
string | Overrides the object key as field name |
Separated Mode
Props are split across parallel objects keyed by field name or path:
| Object Key | Type | Description |
|---|---|---|
fields |
string[] | Field structure (array of paths) |
values |
object | Initial values |
labels |
object | Labels |
placeholders |
object | Placeholders |
defaults |
object | Default values (on reset) |
initials |
object | Initial values (fallback for values) |
disabled |
object | Disabled states |
deleted |
object | Soft-deleted states |
types |
object | Field types |
related |
object | Related field paths |
rules |
object | DVR validation rules |
validators |
object | VJF validation functions |
bindings |
object | Binding keys |
extra |
object | Extra metadata |
options |
object | Field-level options |
hooks |
object | Event hooks |
handlers |
object | Event handlers |
validatedWith |
object | Validation prop overrides |
validators |
object | VJF functions |
observers |
object | MobX observers |
interceptors |
object | MobX interceptors |
input |
object | Input converter functions |
output |
object | Output converter functions |
converters |
object | Converter functions |
computed |
object | Computed value functions |
autoFocus |
object | Auto-focus flags |
inputMode |
object | Input modes |
refs |
object | React refs |
classes |
object | Custom Field classes |
nullable |
object | Nullable flags |
autoComplete |
object | Autocomplete attributes |
Flat Fields
Flat: Unified Mode
const fields = {
username: {
label: 'Username',
value: 'SteveJobs',
placeholder: 'Enter username',
rules: 'required|string|between:5,15',
type: 'text',
disabled: false,
},
email: {
label: 'Email',
value: 's.jobs@apple.com',
rules: 'required|email',
},
};
new Form({ fields });
TypeScript tip: annotate with
Record<string, FieldDefinition>for autocomplete:const fields: Record<string, FieldDefinition> = { username: { label: '...' } };
Shorthand: you can also pass an array of objects with a name key:
const fields = [
{ name: 'username', label: 'Username', value: 'SteveJobs' },
{ name: 'email', label: 'Email', value: 's.jobs@apple.com' },
];
new Form({ fields });
Flat: Separated Mode
Define the field names as an array (or object), then provide props in parallel objects:
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 });
Providing only
values(withoutfieldsarray) will implicitly create the fields:new Form({ values: { username: 'SteveJobs' } }); // field 'username' auto-created
Validation in Separated Mode
// DVR rules
const rules = {
email: 'required|email',
password: 'required|string|min:6',
};
// VJF functions
const validators = {
email: isEmail,
emailConfirm: [isEmail, shouldBeEqualTo('email')],
};
new Form({ fields, rules, validators, ... });
Nested Fields
Nested fields allow grouping fields into objects and arrays. The syntax differs between modes.
Nested: Unified Mode
Use the fields property inside a field definition to create nested sub-fields:
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
nameproperty is required for each field when using array syntax.
Arrays of nested fields:
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({ value: { firstname: 'John', lastname: 'Doe' } });
Related fields across nesting levels:
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
Define the structure using dot notation for nested objects and [] notation for arrays:
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. Array notation
members[]applies to every element in the array.
Property values as nested objects (alternative syntax):
const labels = {
club: {
name: 'Club Name',
city: 'Club City',
},
members: [{
firstname: 'First Name',
lastname: 'Last Name',
}],
};
Mixed Mode (Unified / Separated)
You can mix both modes in the same form definition. Props defined in the unified fields object take precedence over separated props.
new Form({
// Separated mode props
values: {
username: 'SteveJobs',
},
labels: {
username: 'Username',
},
rules: {
username: 'required|string',
},
// Unified mode override for specific fields
fields: {
username: {
type: 'email', // overrides the default 'text' type
disabled: false,
},
},
});
Common Patterns
Fields with Default & Initial Values
const fields = {
newsletter: {
label: 'Subscribe',
type: 'checkbox',
value: true, // initial value on mount
default: false, // value after reset
},
};
Fields with Per-Field Options
Each field can override form-level options:
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
const fields = {
username: {
label: 'Username',
bindings: 'MaterialTextField', // use a registered binding
},
};
Accessing Fields at Runtime
Once the form is created, access fields via $() or select():
form.$('username'); // flat field
form.$('address.city'); // nested field
form.$('members[0].firstname'); // array element by index
form.$('members').$(0).$('firstname'); // chained selector
The
$()method supports transparent autocomplete for both top-level and nested paths when using TypeScript withForm<F>(see TypeScript Usage).