DEV Community

tu6ge
tu6ge

Posted on

I’ve Decided to Redefine Two-Way Binding in Vue

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> 
Enter fullscreen mode Exit fullscreen mode

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> 
Enter fullscreen mode Exit fullscreen mode

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 /> 
Enter fullscreen mode Exit fullscreen mode
  • With Naive UI’s n-input (which uses v-model:value):
<n-input f-model:value /> 
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)