In Vue, we already have the very convenient v-model syntax sugar. It handles two essential things for us:
- When a JavaScript variable has a value, it fills the input field.
- When the input changes, the new value is passed back to JavaScript.
That’s basically what v-model does under the hood.
So why redefine two-way binding?
The reason is that I’ve been exploring a new approach to form validation. I believe the next generation of Vue form validators should be:
- Component-driven – the form behavior is powered by components, not configuration.
- Style-agnostic – no built-in styling, leaving developers free to define layouts.
- Flexible with UI libraries – should work seamlessly with input components from libraries like Naive UI, Element Plus, or even fully custom ones.
What existing validators do
When I looked at existing solutions, I found a common pattern: each form field usually comes with:
- A label
- An input
- A help message
- An error message (shown when validation fails)
Some libraries solve this by bundling their own input components and tightly integrating them with validation. While this works, it reduces flexibility.
I wanted something different: a validator that lets developers use their own UI components, while still handling value binding and validation logic cleanly.
The idea: u-field component
To achieve this, I introduced the idea of a u-field component.
You can put any input component into its slot.
The u-field exposes two key bindings to its slot:
- value passes the form data down to the input.
- update sends the new input value back up to the form.
This makes synchronization possible, and sets the stage for validation.
For example:
<template> <u-field v-slot="{ value, update }"> <input :value="value" @input="($event) => update($event.target.value)" /> </u-field> </template>
Why not just v-model?
Looking at Vue’s official docs, this is exactly the kind of code v-model was meant to simplify.
But when binding with parent components (not just local state), we need another kind of syntax sugar.
So, inspired by vue-macros, I created a Vite plugin to introduce a new directive: f-model
.
With f-model
, the example above becomes:
<template> <u-field v-slot="{ value, update }"> <input f-model /> </u-field> </template>
Beyond text inputs
Of course, text inputs are the simplest case.
For other components like radio buttons, checkboxes, or selects, special handling is still needed.
For third-party components, it’s straightforward:
- With a regular input:
<input f-model />
- With Naive UI’s n-input (which uses v-model:value):
<n-input f-model:value />
vue-uform is still evolving, but I’d love to hear your feedback. Try it out on GitHub or play with the StackBlitz demos, and let me know what you think!
- GitHub: https://github.com/tu6ge/vue-uform/
- StackBlitz: vue-uform & tailwind https://stackblitz.com/edit/vue-uform-tailwind?file=src%2FApp.vue
- StackBlitz: vue-uform & scheme https://stackblitz.com/edit/vue-uform-tailwind-scheme?file=src%2FApp.vue
Top comments (0)