Web Demo


Simple Usage

add dependency

flutter pub add forme
flutter pub add forme_base_fields 

create forme

FormeKey key = FormeKey();// formekey is a global key , also  used to control form
Widget child = FormeTextField(name:'username',decoration:const InputDecoration(labelText:'Username'));
Widget forme = Forme(

Forme Attributes

keyfalseFormeKeya global key, also used to control form
childtrueWidgetform content widget
onFieldStatusChangedfalseFormeFieldStatusChangedlisten form field's status change
initialValuefalseMap<String,Object?>initialValue , will override FormField's initialValue
onWillPopfalseWillPopCallbackSignature for a callback that verifies that it's OK to call Navigator.pop
quietlyValidatefalseboolif this attribute is true , will not display default error text
autovalidateModefalseAutovalidateModeauto validate form mode
autovalidateByOrderfalseboolwhether auto validate form by order
onFieldsRegisteredfalsefunctionlisten registered fields
onFieldsUnregisteredfalsefunctionlisten unregistered fields
onInitialedfalsefunctionused register visitors


attributes supported by all FormeField

nametrueStringfield's id,should be unique in form
readOnlyfalseboolwhether field should be readOnly,default is false
buildertrueFormeFieldBuilderbuild field content
enabledfalsefunctionused to compare field's value
comparatorfalseboolwhether field is enabled , default is true
quietlyValidatetrueboolwhether validate quietly
initialValuetrueTdefault value of field , will overwritten by Forme initialValue
asyncValidatorDebouncefalseDurationasync validate debounce , default is 500ms
autovalidateModefalseAutovalidateModeautovalidate mode
onStatusChangedfalseFormeFieldStatusChangedlisten value
onInitialedfalseFormeFieldInitialedused to register visitors
onSavedfalseFormeFieldSettertriggered when form saved
validatorfalseFormeValidatorsync validator
asyncValidatorfalseFormeAsyncValidatorasync validator
decoratorfalseFormeFieldDecoratorused to decorator a field
orderfalseintorder of field
requestFocusOnUserInteractionfalseboolwhether request focus when field value changed by user interaction
registrablefalseboolwhether this field should be registered to Forme
valueUpdaterfalsefunctionused to update value in didUpdateWidget
validationFilterfalsefunctionused to determine whether perform a validation or not


Async Validation

async validator is supported after Forme 2.5.0 , you can specify an asyncValidator on FormeField , the unique difference between validator and asyncValidator is asyncValidator return a Future<String> and validator return a String

when to perform an async validation

if FormeField.autovalidateMode is AutovalidateMode.disabled , asyncValidator will never be performed unless you call validate on FormeFieldState manually.

if you specify both validator and asyncValidator , asyncValidator will only be performed after validator return null.

if validationFilter is specified and not passed. asyncValidator will not be performed


you can specify a debounce on FormeField , debounce will not worked when you manually call validate on FormeFieldState

whether validation itself is valid

in some cases,when an async validation is performing , another validation on same field is performed,in this case ,previous validation is invalid , so if you want to update UI before return validation result in async validator , you need to validate it first,eg:

    return Future.delayed(const Duration(seconds:2),(){
        if(isUnexceptedValue(value)) {
            return 'invalid';
        return null;


you can use FormeValidates to simplify your validators

Validator NameSupport TypeWhen ValidWhen Invalid
notNulldynamicvalue is not nullvalue is null
sizeIterable Map String1. value is null 2. max & min is null 3. String's length or Collection's size is in min,maxString's length or Collection's size is not in min,max
minnum1. value is null 2. value is bigger than minvalue is smaller than min
maxnum1. value is null 2. value is smaller than maxvalue is bigger than max
notEmptyIterable Map String1. value is not null 2. String's length or Collection's size is bigger than zero1. value is null 2. String's length or Collection's size is zero
notBlankString1. value is null 2. value.trim()'s length is not nullvalue'length is zero after trimed
positivenum1. value is null 2. value is bigger than zerovalue is smaller than or equals zero
positiveOrZeronum1. value is null 2. value is bigger than or equals zerovalue is smaller than zero
negativenum1. value null 2. value is smaller than zerovalue is bigger than or equals zero
negativeOrZeronum1. value null 2. value is smaller than or equals zerovalue is bigger than zero
patternString1. value null 2. value matches patternvalue does not matches pattern
emailString1. value null 2. value is a valid emailvalue is not a valid email
urlString1. value is null 2. value is empty or value is a valid urlvalue is not a valid url
rangenum1. value null 2. value is in rangevalue is out of range
equalsdynamic1. value null 2. value is equals target valuevalue is not equals target value
anyTany validators is validevery validators is invalid
allTall validators is validany validators is invalid

when you use validators from FormeValidates , you must specify at least one errorText , otherwise errorText is an empty string



whether form has a name field

bool hasField = formeKey.hasField(String name);

get field by name

T field = formeKey.field<T extends FormeFieldState>(String name);

get form value

Map<String, Object?> data = formeKey.value;

set form value

formeKey.value = Map<String,Object?> value;


you can use FormeValidateSnapshot.isValueChanged to check whether form value is changed duration this validation , if is changed , typically means this validation is invalid , you should not submit your form even though validation is passed

Future<FormeValidateSnapshot> future = formKey.validate({
    bool quietly = false,
    Set<String> names = const {},
    bool clearError = false,
    bool validateByOrder = false,

get validation

FormeValidation validation = formKey.validation;

reset form


save form


whether validate is quietly

bool quietlyValidate = formKey.quietlyValidate;

is value changed after initialed

bool isValueChanged = formeKey.isValueChanged

get all fields

List<FormeFieldState> fields = formeKey.fields;

add visitor

formeKey.addVisitor(FormeVisitor visitor);

remove visitor

formeKey.removeVisitor(FormeVisitor visitor);

rebuild form

rebuild all widgets in Forme



get field's name

String name = field.name

whether current field is readOnly

bool readOnly = field.readOnly;

set readOnly on field

field.readOnly = bool readOnly;

whether current field is enabled

bool enabled = field.enabled;

set enabled on field

field.enabled = bool enabled;

get focusNode

FocusNode focusNode = field.focusNode;

get value

T value = field.value;

set value

field.value = T data;

reset field



Future<FormeFieldValidateSnapshot> future = field.validate({bool quietly = false});

get validation

FormeFieldValidation validation = field.validation;

get oldValue

if value changed , you can use this method to get previous value

T? value = field.oldValue;

is value changed

bool isValueChanged = field.isValueChanged

get generic type

Type type = field.type;

whether field value is nullable

bool isNullable = field.isNullable;

get FormeState

FormeState? form = field.form;

get status

FormeFieldStatus<T> get status = field.status;

is custom validation

whether current validation is set via errorText

bool isCustomValidation = field.isCustomValidation;

get error text

typically used in display.

if FormeField.quietlyValidate or Forme.quietlyValidate is true , you'll always get null

String? errorText = field.errorText;

set error text

if errorText is null , reset validation.

field will rebuild after this method called. if field has validators , a new validation maybe performed , in this case ,custom validation will be overwritten by new validation. use FormeField.validationFilter to avoid this

will not worked on disabled fields

field.errorText = 'custom error';

add visitor

field.addVisitor(FormeFieldVisitor visitor);

remove visitor

field.removeVisitor(FormeFieldVisitor visitor);


listeners is helpful when you build widgets which depends on status of FormeField or Forme , you must used them inside in Forme or FormeField


will rebuild whenever field's status changed , use filter to avoid unnecessary rebuild.


            filter:(status) => status.isValueChanged,/// if you only want to  rebuild when value changed
                return Text('current value:${status.value}')


will rebuild whenever validation of any field changed


      names: const {'password', 'confirm'},
      builder: (context, validation) {
        if (validation == null) {
          return const SizedBox();
        if (validation.isInvalid) {
          return Padding(
            padding: const EdgeInsets.only(left: 24),
            child: Text(
                  .where((element) => element.isInvalid)
              style: _getErrorStyle(),
        return const SizedBox.shrink();


will rebuild whenever form validation changed , useful when you want to create a submit button which only clickable when form validation passed


                return TextButton(
                    onPressed:validation.isValid ? submit:null,
                    child:const Text('submit'),


will rebuild whenever form value changed , useful when you want to create a reset button which depends on form value changed or not


                return TextButton(
                    onPressed:isValueChanged ? reset:null,
                    child:const Text('reset'),


will rebuild whenever form value changed , typically used in debug .


                return Text(value.toString());

custom field

    name: 'customField',
    initialValue: 'currentValue',
    builder: (FormeFieldState<String> state) {
        return TextButton(
        onPressed: () {
           state.readOnly? null: state.didChange('newValue');
        child: Text(state.value),