Introduction
NotForm handles the parts of a form that are tedious to build by hand — validation, dirty and touched tracking, per-field triggers, and array manipulation — while staying out of the way of your markup and styling.
Most of what NotForm does happens without opinions about your markup. <NotField> and <NotArrayField> are fully renderless — they render nothing of their own, only exposing reactive state and event handlers through their default slot, so you decide entirely what markup represents a field. <NotForm> and <NotMessage> do render something — a native <form> element and a small error-message element, respectively — but neither ships any styling, and both let you substitute your own markup: <NotForm> accepts native form attributes directly, and <NotMessage>'s as prop lets you swap in your own element or component.
Why NotForm?
Building forms in Vue usually means choosing between:
- A UI library that dictates your markup and styling, or
- A validation library that requires a lot of boilerplate to wire into your components
NotForm tries to avoid both trade-offs. It only owns form state — values, errors, touched/dirty flags, and validation triggers — and leaves rendering entirely up to you.
Schema-agnostic Validation
Drop in any Standard Schema compliant validator — Zod, Valibot, ArkType, and more. Swap libraries without touching your form logic.
Granular State Tracking
Fields are compared against initial values on every keystroke. Touched and dirty states are managed automatically.
Dynamic Array Fields
Append, prepend, insert, remove, swap, and move array items with stable keys that survive reorders.
End-to-End Type Safety
Field paths and their value types are strictly inferred from your schema. Invalid paths trigger compile-time errors.
Why Standard Schema?
NotForm doesn't implement its own validation language. Rather than inventing another set of validation rules, it validates against any schema that implements the Standard Schema interface.
A form library needs a consistent way to:
- validate values
- receive validation issues in a predictable shape
- support asynchronous validation, such as checking username availability against an API
- understand a schema's input and output types, since a schema can transform data on the way out (e.g.
z.coerce.number())
Standard Schema provides exactly that contract, so NotForm only ever talks to your validator through it. That means:
- The rest of the NotForm API —
form.values,form.errors,validate(),submit(), field paths, and so on — stays identical no matter which validator you choose. - You can swap validators later without rewriting your forms.
- Your schema is never mutated or wrapped, so it stays reusable outside of NotForm too.
NotForm works with any Standard Schema-compatible validator, including:
import { z } from 'zod'
const schema = z.object({
age: z.number().min(18, 'Must be 18 or older'),
email: z.email('Invalid email'),
})
import {
email,
minValue,
number,
object,
string,
} from 'valibot'
const schema = object({
age: number([minValue(18, 'Must be 18 or older')]),
email: string([email('Invalid email')]),
})
import { type } from 'arktype'
const schema = type({
age: 'number>=18',
email: 'email',
})
Pass your schema straight to useNotForm and the rest of the NotForm API — values, errors, submission, and field-level typing — is derived from it automatically.
Next steps
Ready to dive in?
- Try the Playground for a browser-based example.
- Or continue to Installation to add NotForm to your project.