FieldDescriptor
A FieldDescriptor defines the behavior of a single field within a form model. Descriptors are passed to createModel in the descriptors object.
Interface
interface FieldDescriptor<T, K> {
value?: T;
required?: boolean | string;
validator?: ValidateFn<T, K> | ValidateFn<T, K>[];
autoValidate?: boolean;
waitForBlur?: boolean;
disabled?: boolean;
errorMessage?: string;
clearErrorOnValueChange?: boolean;
validationDebounceThreshold?: number;
hasValue?: (value: T) => boolean;
meta?: Record<string, any>;
}
Properties
value
value?: T
Initial value for the field. Can also be provided via initialState in createModel.
required
required?: boolean | string
Marks the field as required.
true— uses default message:Field: "fieldName" is required'Custom message'— uses the provided string as the error message
Required validation runs before custom validators.
validator
validator?: ValidateFn<T, K> | ValidateFn<T, K>[]
Validation function(s) for the field. Can be:
- A single function
- An array of functions (runs sequentially)
- Sync or async
See Validation Guide for detailed examples.
autoValidate
autoValidate?: boolean // default: true
When true, validation runs automatically after each value change (debounced).
waitForBlur
waitForBlur?: boolean // default: false
When true, validation is deferred until markBlurredAndValidate() is called (typically on onBlur).
disabled
disabled?: boolean // default: false
When true, the field is disabled:
- Skipped during validation
requiredreturnsfalse
errorMessage
errorMessage?: string
Default error message when a validator returns false (without a specific message).
clearErrorOnValueChange
clearErrorOnValueChange?: boolean // default: false
When true, the error is immediately cleared when the user changes the value, without waiting for debounced re-validation. Provides faster visual feedback.
validationDebounceThreshold
validationDebounceThreshold?: number // default: 300 (ms)
How long to debounce auto-validation after value changes. Only applies when autoValidate is true.
hasValue
hasValue?: (value: T) => boolean
Custom function to determine if the field "has a value". Used by the required check.
Default behavior:
- Arrays:
value.length > 0 - Scalars: not
null,undefined, or""
meta
meta?: Record<string, any>
Arbitrary metadata stored on the field. Useful for UI hints like placeholder text, dropdown options, or layout preferences.
email: {
required: true,
meta: {
placeholder: 'Enter your email',
icon: 'mail',
order: 1,
},
}
// Access later:
form.fields.email.meta?.placeholder; // 'Enter your email'
Full Example
const form = createModel<SignupForm>({
descriptors: {
username: {
required: 'Username is required',
autoValidate: true,
validationDebounceThreshold: 500,
validator: async ({ value }) => {
const taken = await checkUsernameTaken(value);
if (taken) throw new Error('Username is taken');
},
},
email: {
required: 'Email is required',
waitForBlur: true,
clearErrorOnValueChange: true,
validator: ({ value }) => {
if (!value.includes('@')) {
return { error: 'Invalid email address' };
}
},
},
role: {
value: 'user',
meta: {
options: ['user', 'admin', 'editor'],
},
},
tags: {
value: [],
required: 'Select at least one tag',
hasValue: (v) => Array.isArray(v) && v.length > 0,
},
notes: {
disabled: true, // initially hidden/disabled
},
},
initialState: {
username: '',
email: '',
role: 'user',
tags: [],
notes: '',
},
});