Skip to content

Standalone Fields

Not every validated input lives inside a <form>. The initField() function lets you enhance individual fields independently — useful for search bars, inline edits, or widget inputs.

<div data-form-field="search"
data-validate='[{"type":"StringLength","options":{"minimum":2,"message":"Enter at least 2 characters"}}]'>
<label for="search">Search</label>
<input id="search" type="text" />
<div id="search-errors" class="invalid-feedback"></div>
</div>
import { initField, registerDefaultValidators } from 'formlayer';
registerDefaultValidators();
const ctrl = initField(document.getElementById('search')!);
ctrl.on('valid', (state) => {
performSearch(state.value);
});
Live Preview

Pass an explicit field class — standalone fields do not use the global field-type registry or data-field-type auto-discovery:

import { ComboboxField } from 'formlayer-plugin-combobox';
const comboCtrl = initField(mySelectWrapper, { field: ComboboxField });

For lazy-loaded custom fields:

const comboCtrl = await initFieldAsync(
mySelectWrapper,
() => import('formlayer-plugin-combobox'),
);

initField and initFieldAsync construct the field and run its DOM setup (mount()) in one step, so the returned controller is ready to use immediately. Custom fields built on AbstractDomFormField are initialized this way — do not call new MyField(wrapper) yourself, as a bare instance is constructed but not mounted.

Pass an InitFieldOptions object as the second argument:

const ctrl = initField(myElement, {
validate(value, rules, defaultValidate) {
const result = defaultValidate();
if (result.isValid && value.includes('admin')) {
return { isValid: false, errors: ['Reserved word'] };
}
return result;
},
onServerErrors(errors, fieldName) {
return errors.map(e => `Server: ${e}`);
},
errorsSelector: '[data-field-error]',
renderError: ({ message }) => `<span class="field-error">${message}</span>`,
errorsSeparator: '',
});

For icons, custom container placement, form-level summaries, and the full option reference, see Error Rendering.

initField() returns a FieldController with full control:

const ctrl = initField(myInput);
// Read state
console.log(ctrl.state);
// { name: "search", value: "", isValid: true, isDirty: false, isTouched: false, errors: [] }
// Set value programmatically
ctrl.setValue('hello');
// Trigger validation
const result = ctrl.validate();
console.log(result.isValid, result.errors);
// Listen for changes
ctrl.on('change', (state) => {
console.log('New value:', state.value);
});
// Reset to initial state
ctrl.reset();
// Clean up
ctrl.destroy();

You can pass an <input> directly — initField resolves the closest [data-form-field] wrapper:

const input = document.querySelector('#search input') as HTMLInputElement;
const ctrl = initField(input);

If the input is not inside a [data-form-field] wrapper, initField throws an error.