Writer: Jerry Akira 日付: December 10, 2025
Start
Hello everyone !
I’m Akira (@JerryAkira) from ProOne Dev team.
It’s annual MeetsMore sharing time so I’d like to take this chance to share our new Form system designed by a project called V-Next in ProOne.
Please feel free to check out others’ shares here MeetsMore Advent Calendar 2025 - Adventar .

Why
For a B2B SaaS product, forms are indispensable. In traditional systems, we often use react-hook-form together with:
- Custom
Inputcomponents to build a form - A custom
onSubmitto handle submission - A custom
onChangeto validate content and surface errors
This is simple and quick for a tiny project. But for an enterprise‑grade product, many forms and many custom handlers lead to:
No unified standards. Anyone might create yet another custom form approachReduced feature reliabilityHigher code complexityRapidly increasing maintenance costsdue to the aboveLower overall development efficiencyfrom duplicated work
To avoid rising maintenance costs and declining development efficiency—the two issues enterprises care about most—we designed a new Form system in our platform.
What
The system supports both server‑driven and client‑side rendering. Core capabilities include:
Form FieldsdefinitionDefault valueinitializationGrid layoutconfigurationType safety- Built‑in
base validationsprovided by the system onSubmitwith value type‑safety based on the definitiononValidValueChange, which only fires when the current value is valid- Additional
custom validations
Next, I’ll introduce the minimal configuration ( basic elements ) of a form and a practical example to show how this Form system is used.
Basic Elements
1. Fields - The core part of a form
In V‑Next, the Form system continually ships and supports foundational Fields (think input components), such as TextField, NumberField, StaticSelectField, and DynamicSelectField. Each Field has its own spec, value type, error type, and we define a Field through configuration.
For example, a TextField that is required, has label "Name", description "I'm a text field", and grid size 12, where the key in the form value is name, can be defined as
{ "field": "name", "kind": "text", "description": "I'm a text field", "required": true, "label": "Name", "grid": { "size": 12 } }
It will be rendered as

At this point, the form value is:
{ name: '123' }
When you click the Save button, because required: true is set, the system automatically validates this Field (V‑Next Form provides auto‑validation based on base Field definitions). If nothing is entered, an error is shown:

2. Default value of the form
As another core part of a form, when defining a Form, we provide a defaultValue as the initial value that drives the first render of the entire Form.
V‑Next Form can auto‑generate initial empty values based on the base Field definitions. If we want to set defaultValue manually, the V‑Next Form type‑safety system runs checks:
Key existence check against fields.
For example, if we configured a text field with field: 'name', then defaultValue should include a key name. If it’s missing, an error is shown.

Type check per key.
For example, if we configured a Field with kind: 'text', the initial value should be a string (as defined by TextField). If we pass [], a type error is shown.

Therefore, we should define it like this:

And we’ll end up with a simple Form like this:

3. onSubmit event
V‑Next Form includes a built‑in Save button (configuable). After you finish filling out the form and click Save, the Form first validates according to each Field’s configuration and the base rules defined by the foundational Fields, then distributes errors.
From the example above, name is required, and age has a minimum of 0 and a maximum of 100.
If name is empty and age is 111, clicking Save will automatically surface two errors.

💡 Tip: you don’t need to write any validation rules for this. The system performs the basic validations for you.
4. Custom Validation
Currently, V‑Next Form only includes built‑in base validations at the Field level. However, we may need cross‑field validation with more complex logic. In that case, the Form supports an additional custom validation function.
Using the same form as above, suppose we want to restrict that Jim can only be 68 years old. We can pass a validate rule like this (type of formValue will be automatically inferred based on fields definition)


And we can do a cross validation with type‑safety value in validate.

Practical Example
Christmas is just around the corner, so I’d like to wrap up with a real form example to show you how to use the basic elements I mentioned above.
Suppose Santa wants to run a company‑wide survey to learn what gifts everyone wants. The survey looks like this:

To implement the form with basic and cross validation, we only need to write 103 lines of definition code like:
<Form grid={{ columns: 12, spacing: 2, container: true }} fields={ [ { field: 'name', kind: 'text', label: 'Child Name', required: true, grid: { size: 5 }, }, { field: 'gender', kind: 'static_select', label: 'Gender', maxSelection: 1, options: [ { label: 'Boy', value: 'boy' }, { label: 'Girl', value: 'girl' }, ], grid: { size: 3 }, }, { field: 'age', kind: 'number', label: 'Age', min: 0, max: 18, grid: { size: 3 }, }, { field: 'address', kind: 'text', label: 'Address', multiline: true, required: true, grid: { size: 11 }, }, { field: 'gifts', kind: 'composite', fields: [ { field: 'type', kind: 'static_select', label: 'Gift Type', maxSelection: 1, options: [ { label: 'Toy', value: 'toy' }, { label: 'Book', value: 'book' }, { label: 'Other', value: 'other' }, ], grid: { size: 4 }, }, { field: 'color', kind: 'static_select', label: 'Preferred Color', maxSelection: 1, options: [ { label: 'Red', value: 'red' }, { label: 'Blue', value: 'blue' }, { label: 'Pink', value: 'pink' }, ], grid: { size: 4 }, }, { field: 'notes', kind: 'text', label: 'Notes', grid: { size: 3 }, }, ], array: { append: { value: { type: null, color: null, notes: null }, grid: { size: 11 }, }, remove: { grid: { size: 1 } }, }, }, ] as const satisfies FormFieldDefinition[] } defaultValue={{ name: '', gender: null, age: null, address: '', school: { name: null, grade: null }, gifts: [{ type: null, color: null, notes: null }], }} onSubmit={(value) => console.info(value)} validate={(value) => { const error = { gifts: value.gifts.map((gift) => gift.type === 'toy' && gift.color === 'red' ? { color: { message: 'no red toy left' } } : undefined, ), } return error.gifts.some((err) => err) ? error : undefined }} />
With this code, we can get a standard V‑Next Form with built‑in basic validations, and then use a custom onSubmit to handle the input data!
At last
If this is interesting and you want to join MeetsMore or want to know more, we are actively hiring engineers right now: https://corp.meetsmore.com/.
Thanks for reading.