Introduction
NotForm is a headless, schema-driven form library for Vue 3 and Nuxt. See what it does, what it leaves to you, and where to start.
NotForm manages form state for Vue 3 and Nuxt. You describe your data with a schema, NotForm turns it into a typed reactive form, and you keep full control of the markup and styling.
<script setup lang="ts">
import { Field, FieldError, Form, useForm } from 'notform'
import { z } from 'zod'
const form = useForm({
initialValues: { email: '', message: '' },
async onSubmit(data) {
await fetch('/api/contact', {
body: JSON.stringify(data),
headers: { 'content-type': 'application/json' },
method: 'POST',
})
},
schema: z.object({
email: z.email('Enter a valid email address.'),
message: z.string().min(10, 'Write at least 10 characters.'),
}),
})
</script>
<template>
<Form :form="form">
<Field
v-slot="{ field }"
:field="form.field('email')"
>
<label :for="field.path">Email</label>
<input
:id="field.path"
v-model="field.model"
v-bind="field.events"
type="email"
>
<FieldError :errors="field" />
</Field>
<Field
v-slot="{ field }"
:field="form.field('message')"
>
<label :for="field.path">Message</label>
<textarea
:id="field.path"
v-model="field.model"
v-bind="field.events"
/>
<FieldError :errors="field" />
</Field>
<button
type="submit"
:disabled="form.isSubmitting"
>
Send
</button>
</Form>
</template>
What you get
- Types from your schema. Field paths, field values,
form.values, and the data passed toonSubmitare all inferred. A mistyped path is a compile error. - Validation you can tune. Choose, per field, whether blur, change, or input triggers validation, whether errors clear eagerly, and how long to debounce.
- Stable fields.
form.field(path)always returns the same instance for the same path, so touched state and pending validation survive re-renders. - Repeatable sections. Array fields give every item a stable identity and keep errors attached to the right item when you add, remove, move, or swap.
- Plain, reactive state.
isDirty,isSubmitting,isValidating, and the rest are ordinary reactive properties. There is nothing to subscribe to. - Any validator. Zod, Valibot, ArkType, or any other library that implements Standard Schema.
What NotForm leaves to you
- UI. NotForm renders a native
<form>and nothing else. Inputs, labels, layout, and styling are yours, or your component library's. - Validation rules. Rules are written in your schema, not in NotForm.
- Networking.
onSubmitreceives validated data. How you send it is up to you. - Global state. Every
useFormcall creates an independent form. There is no store to configure.
Packages
| Package | Use it for |
|---|---|
notform | Vue 3 and Vite applications. Import components and useForm explicitly. |
@notform/nuxt | Nuxt 4 applications. Registers the same components and useForm as auto-imports. |
These docs describe NotForm v4. If your project uses NotForm 3.x, start with the v3 migration guide.
How this docs is organized
Getting started
Install NotForm, build your first form, and learn the mental model.
Guides
Task-oriented explanations of binding inputs, validation, errors, submission, state, and arrays.
Recipes
Complete solutions to common problems such as server errors, async checks, and multi-step forms.
Reference
Exact contracts for components, useForm, and every public type.
Suggested reading order
- Installation
- Quickstart
- Core concepts
- Binding inputs and Validation
- Whatever your form needs next: errors, array fields, or a recipe
Building with an AI coding assistant? See Working with AI.