Philosophy
The Vyuh Form system is built on six design principles that shape every API decision.
1. Declarative over Imperative
Forms are declared, not programmed. You describe what the form should be -- its fields, validations, and conditional rules -- and the system handles how to render, validate, and manage state.
// Declarative: describe the form
final form = FormBuilder(title: 'Registration')
.text('name', title: 'Name').required()
.text('email', title: 'Email').email()
.build();
// The system handles:
// - Creating FormControls with correct types
// - Wiring validators to reactive_forms
// - Managing touched/dirty state
// - Displaying errors at the right timeThis applies equally to the JSON-serialized form model (from a CMS or visual editor) and to the FormBuilder DSL. Both produce the same Form object.
2. Flat FormGroup
All fields in a form share a single FormGroup, regardless of visual nesting. FormSection is purely structural -- it controls visual grouping, not data structure.
// Sections group visually, but fields are flat
FormBuilder(title: 'Survey')
.section('Demographics', (s) => s
.text('age', title: 'Age')
.text('city', title: 'City'))
.section('Feedback', (s) => s
.text('rating', title: 'Rating')
.text('comment', title: 'Comment'))
.build();
// FormGroup has: {age, city, rating, comment}
// NOT: {demographics: {age, city}, feedback: {rating, comment}}This simplifies cross-field validation, rule conditions, and value access. Field names must be unique across the entire form.
3. Rule-Driven Behavior
Dynamic behavior is expressed as rules -- pairs of conditions and actions that react to form state. Rules are data, not code, which means they can be:
- Defined in a CMS
- Created in the visual editor
- Built with the DSL
- Serialized to/from JSON
// Condition: enrollment_type equals 'paid'
// Action: show the payment_method field
.showWhen(When.fieldEquals('enrollment_type', 'paid'))
// The runtime rule system supports these condition models:
// Boolean, String, Numeric, Date, Empty, Compound
// And these action models:
// Visibility, Enabled, Focus, SetValue, Validation, CompoundRules are evaluated reactively -- when a dependent field changes, the rule re-evaluates and the action is applied.
4. Descriptor-Based Registration
Every form component (field type, validation, condition, action) is registered through descriptors. This follows the same pattern used throughout the Vyuh framework.
// Feature registration uses FormDescriptor
FormDescriptor(
validations: [
RequiredValidation.typeDescriptor,
EmailValidation.typeDescriptor,
// ...
],
conditions: [
BooleanCondition.typeDescriptor,
StringCondition.typeDescriptor,
// ...
],
actions: [
VisibilityAction.typeDescriptor,
EnabledAction.typeDescriptor,
// ...
],
)This makes the system fully extensible -- add custom field types, validations, or conditions by registering new descriptors.
5. Runtime vs Editor Separation
The form system is split into two packages with distinct responsibilities:
| Concern | vyuh_feature_forms | vyuh_form_editor |
|---|---|---|
| Purpose | Render and manage forms | Design forms visually |
| State | FormGroup (reactive_forms) | FormEditorStore (MobX) |
| Output | Live form with validation | JSON form definition |
| Users | End users filling forms | Developers/designers building forms |
The editor produces JSON that the runtime consumes. They share the same schema types (vyuh.form, vyuh.formfield.text, etc.) but have completely independent state management.
6. Rows When You Need Explicit Layout
Simple forms can list fields directly. When a layout needs side-by-side fields, use a FormRowBlock at runtime or a Row block in the visual editor. Rows are structural: each FormRowItem wraps a field and a 1-12 column span. Fields themselves do not own a width property.
FormRowBlock(
items: [
FormRowItem(field: TextField(name: 'first', title: 'First'), span: 6),
FormRowItem(field: TextField(name: 'last', title: 'Last'), span: 6),
],
)Form spacing is configurable at the form level (hSpacing, vSpacing) and can be overridden at section or repeating-section level.
Summary
| Principle | Key Idea |
|---|---|
| Declarative | Describe what, not how |
| Flat FormGroup | Single source of truth for all field values |
| Rule-Driven | Conditions + actions as data, not code |
| Descriptor-Based | Extensible registration via TypeDescriptor |
| Runtime/Editor Split | Two packages, one schema, independent concerns |
| 12-Column Grid | Flexible responsive layout system |
Next Steps
- Architecture -- How the packages and descriptors fit together
- Form Model -- The data structures behind forms
- FormBuilder DSL -- The fluent API in detail