Skip to content

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.

dart
// 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 time

This 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.

dart
// 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
dart
// 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, Compound

Rules 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.

dart
// 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:

Concernvyuh_feature_formsvyuh_form_editor
PurposeRender and manage formsDesign forms visually
StateFormGroup (reactive_forms)FormEditorStore (MobX)
OutputLive form with validationJSON form definition
UsersEnd users filling formsDevelopers/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.

dart
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

PrincipleKey Idea
DeclarativeDescribe what, not how
Flat FormGroupSingle source of truth for all field values
Rule-DrivenConditions + actions as data, not code
Descriptor-BasedExtensible registration via TypeDescriptor
Runtime/Editor SplitTwo packages, one schema, independent concerns
12-Column GridFlexible responsive layout system

Next Steps