Form Model
The form model consists of runtime content items that define form structure: Form, FormRowBlock, FormSection, RepeatingSection, and StepForm.
Form
Form is the top-level container and the single source of truth for all form state. It extends ContentItem and creates a FormGroup from its items.
class Form extends ContentItem {
static const schemaName = 'vyuh.form';
final String formId; // Unique identifier (auto-generated via nanoid)
final String? title;
final String? description;
final double hSpacing; // Horizontal grid spacing (default: 16.0)
final double vSpacing; // Vertical grid spacing (default: 16.0)
final List<ContentItem> items; // Fields, rows, sections, repeating sections
final List<FormAsyncValidationConfiguration>? formAsyncValidations;
final List<FormValidationConfiguration>? formValidations;
late final FormGroup formGroup; // reactive_forms FormGroup
final Observable<bool> valid; // MobX-reactive validity
}Creating a Form
Via the DSL:
final form = FormBuilder(title: 'Course Enrollment')
.text('name', title: 'Student Name').required()
.text('email', title: 'Email').email()
.build();Via constructor (typically from JSON):
final form = Form(
title: 'Course Enrollment',
items: [
TextField(name: 'name', title: 'Student Name', validations: [...]),
TextField(name: 'email', title: 'Email', validations: [...]),
],
);Form API
| Method | Description |
|---|---|
currentValues | Returns Map<String, dynamic> of all current field values |
getFieldValue(name) | Returns the value of a specific field |
setFieldValue(name, value) | Sets a single field's value |
patchValues(map) | Patches multiple field values at once |
validate() | Marks all fields as touched, returns bool |
saveAndValidate() | Alias for validate() (values are always live) |
isValid | Synchronous validity check |
resetForm() | Resets to initial values, clears validation state |
isDirty | Whether any field was modified |
isTouched | Whether any field was interacted with |
validateAllAsync() | Runs all async validations, returns Future<bool> |
getAllErrors() | Returns Map<String, String> of all validation errors |
collectSoftBreaches() | Returns List<SoftBreachDetails> of soft validation warnings |
addListener(callback) | Fires when form values change |
dispose() | Cleans up all subscriptions |
Initial Values
You can pre-populate a form at construction time or after:
// At construction (via DSL)
final form = FormBuilder(title: 'Edit Student')
.text('name', title: 'Name').required()
.text('email', title: 'Email').email()
.build(initialValues: {'name': 'Alice', 'email': 'alice@lms.edu'});
// After construction
form.initialValues = {'name': 'Bob', 'email': 'bob@lms.edu'};Setting initialValues patches the FormGroup and resets touched/pristine state so initial values do not trigger error display.
FormSection
FormSection is a structural container that groups fields visually. It is not a FormField and creates no form control.
class FormSection extends ContentItem {
static const schemaName = 'vyuh.form.section';
final String title;
final String? description;
final double? hSpacing; // Null inherits from parent form
final double? vSpacing;
final bool collapsible;
final bool initiallyCollapsed;
final List<ContentItem> items; // Child fields
final List<FormFieldRule> rules; // Section-level visibility rules
}Key characteristics:
- Flat FormGroup -- Fields inside a section are registered in the parent Form's FormGroup, not in a nested group.
- Visual only -- Sections affect rendering (title, description, collapse) but not data structure.
- Section rules -- Sections can have visibility rules that show/hide all children together.
- Spacing override -- Sections can override the parent form's spacing.
// Via DSL
FormBuilder(title: 'Application')
.section('Personal Info', (s) => s
.text('first_name', title: 'First Name')
.text('last_name', title: 'Last Name')
.text('email', title: 'Email'),
collapsible: true,
description: 'Your contact information',
)
.section('Employment', (s) => s
.text('company', title: 'Company')
.text('position', title: 'Position'),
)
.build();
// FormGroup has: {first_name, last_name, email, company, position}FormRowBlock
FormRowBlock is a structural row. It is a ContentItem, not a FormField, so it creates no control and holds no value. Each child is a FormRowItem wrapper containing a FormField plus a span.
FormRowBlock(
items: [
FormRowItem(
field: TextField(name: 'first_name', title: 'First Name'),
span: 6,
),
FormRowItem(
field: TextField(name: 'last_name', title: 'Last Name'),
span: 6,
),
],
)Rows pack fields left-to-right in declaration order. There is no start column and no field-level width property; row width is represented only by the FormRowItem.span values. The visual editor mirrors this runtime model with RowInstance, where row children are FieldInstances and spans are stored by field id.
RepeatingSection
RepeatingSection holds a template of fields that can be repeated N times (dynamic add/remove). It uses a FormArray in the FormGroup.
class RepeatingSection extends ContentItem {
static const schemaName = 'vyuh.form.repeating_section';
final String title;
final String? description;
final String name; // Namespace for field names
final List<ContentItem> template; // Template fields
final int minInstances; // Minimum copies (default: 1)
final int? maxInstances; // Maximum copies (null = unlimited)
final RepeatDisplayMode displayMode; // accordion or expanded
final bool collapsible;
}The collected values output as an array:
{
"modules": [
{ "module_title": "Introduction", "duration": 2 },
{ "module_title": "Advanced Topics", "duration": 4 }
]
}Field names are namespaced: modules.0.module_title, modules.1.module_title, etc.
// Repeating section for course modules
RepeatingSection(
title: 'Course Modules',
name: 'modules',
minInstances: 1,
maxInstances: 10,
displayMode: RepeatDisplayMode.accordion,
template: [
TextField(name: 'module_title', title: 'Module Title', validations: [...]),
NumberField(name: 'duration', title: 'Duration (hours)', validations: [...]),
],
)StepForm
StepForm is a multi-step wizard where each step is a complete Form. It supports sequential navigation, progress tracking, and draft saving.
final class StepForm extends ContentItem {
static const schemaName = 'vyuh.stepForm';
final String? title;
final StepFormControllerConfiguration controller;
final Action? submitAction;
final List<Form> steps;
final ProgressTrackingType trackProgress; // none, all, required
final bool allowDrafts;
final bool isSequential;
final bool showStepStatus;
}ProgressTrackingType
| Value | Behavior |
|---|---|
none | No progress tracking |
all | Track completion of all fields |
required | Track only required field completion |
StepForm Layouts
| Layout | Description |
|---|---|
DefaultStepFormLayout | Horizontal step indicator with animated transitions |
VerticalStepperStepFormLayout | Material vertical stepper |
SingleStepLayout | Shows one step at a time, no indicator |
StepFormDescriptor
Register custom controllers via StepFormDescriptor:
StepFormDescriptor(
layouts: [
DefaultStepFormLayout.typeDescriptor,
VerticalStepperStepFormLayout.typeDescriptor,
SingleStepLayout.typeDescriptor,
],
controllers: [
MyCustomController.typeDescriptor,
],
)Container Hierarchy
Next Steps
- Field Types -- All 12 field types with constructors
- Validation -- How validation is wired to the FormGroup
- Rules System -- Conditions and actions for dynamic behavior