Get Started β
Vue-uForm is an unstyled, flexible form validation library for Vue 3, focusing on component-driven design.
- π¨ Works with any UI component library
- π§© Fully customizable layout (label, input, help info, validation messages, etc.)
- π¦ Apply layouts globally to all form fields
- π Customizable submit button
- β¨ Minimal JS/TS code β most logic stays in components
- β Built-in and custom validation rules
Why is vue-uform? β
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 />
Installation β
pnpm install vue-uform
pnpm install @vue-uform/vite-plugin -D
Configure β
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import uForm from "@vue-uform/vite-plugin";
export default defineConfig({
plugins: [vue(), uForm()],
});
global register components
// ./src/main.ts
import { plugin as VueuForm } from "vue-uform";
const app = createApp(App);
app.use(VueuForm).mount("#app");
or import components in Vue SFC file
<script setup>
import { Form, Field, Submit, Reset } from "vue-uform";
</script>
Example β
<script setup>
// init form values
const formValues = {
username: "",
password: "",
};
// handler form submit event
function doLogin(data) {
console.log(data);
}
</script>
<template>
<u-form :values="formValues" @submit="doLogin">
<u-field
name="username"
label="Username"
validation="required"
v-slot="{ value, update }"
>
<input f-model />
</u-field>
<u-field
name="password"
label="Password"
validation="required"
v-slot="{ value, update }"
>
<input type="password" f-model />
</u-field>
<u-submit>Login</u-submit>
<!-- or -->
<u-submit custom v-slot="{ submit }">
<button type="button" :style="{ color: 'blue' }" @click="submit">
Login
</button>
</u-submit>
</u-form>
</template>