# Introduction ::note NotForm is a lightweight form state and validation library built specifically for the Vue 3 Composition API. :: 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. ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} and ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} 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. ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} and ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} do render something — a native `
`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} element and a small error-message element, respectively — but neither ships any styling, and both let you substitute your own markup: ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} accepts native form attributes directly, and ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"}'s `as`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} 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. ::card-group :::app-card{icon="tabler:shield-check" title="Schema-agnostic Validation"} Drop in any Standard Schema compliant validator — Zod, Valibot, ArkType, and more. Swap libraries without touching your form logic. ::: :::app-card{icon="tabler:pencil" title="Granular State Tracking"} Fields are compared against initial values on every keystroke. Touched and dirty states are managed automatically. ::: :::app-card{icon="tabler:list" title="Dynamic Array Fields"} Append, prepend, insert, remove, swap, and move array items with stable keys that survive reorders. ::: :::app-card{icon="tabler:braces" title="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](https://standardschema.dev){rel=""nofollow""} 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()`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"}) 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`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"}, `form.errors`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"}, `validate()`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"}, `submit()`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"}, 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: ::code-group ```ts [Zod] import { z } from 'zod' const schema = z.object({ age: z.number().min(18, 'Must be 18 or older'), email: z.email('Invalid email'), }) ``` ```ts [Valibot] 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')]), }) ``` ```ts [ArkType] import { type } from 'arktype' const schema = type({ age: 'number>=18', email: 'email', }) ``` :: Pass your schema straight to [`useNotForm`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"}](https://notformdocs.vercel.app/docs/composables/use-not-form) 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](https://notformdocs.vercel.app/playground) for a browser-based example. - Or continue to [Installation](https://notformdocs.vercel.app/docs/getting-started/installation) to add NotForm to your project. # Installation NotForm is distributed as a single npm package, `notform`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"}, and can be used directly in any Vue 3 application — no build plugin, compiler macro, or bundler configuration required. ::note If you are using Nuxt, skip ahead to the [Nuxt](https://notformdocs.vercel.app/#nuxt) section below and use the official [Nuxt Module](https://notformdocs.vercel.app/docs/getting-started/nuxt-module) instead of the manual steps here. It handles imports and setup for you. :: ## Install the package NotForm validates values using [Standard Schema](https://standardschema.dev){rel=""nofollow""} rather than a validation language of its own. This means NotForm doesn't ship a validator — you bring your own compatible library alongside it. Popular options include [Zod](https://zod.dev){rel=""nofollow""}, [Valibot](https://valibot.dev){rel=""nofollow""}, and [ArkType](https://arktype.io){rel=""nofollow""}. Pick one of these (this guide uses `zod`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} in its examples) and install it together with `notform`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} using your preferred package manager: ::code-group ```bash [pnpm] pnpm add notform zod ``` ```bash [yarn] yarn add notform zod ``` ```bash [npm] npm install notform zod ``` ```bash [bun] bun add notform zod ``` :: ::note You can replace `zod`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} with another Standard Schema-compatible validation library if you prefer. Every code example in these docs uses `zod`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"}, but nothing about NotForm's API changes based on which validator you choose. :: ## Vue and Vite NotForm is designed for Vue 3 and works with standard Vue applications out of the box — there is no application-level setup step, plugin installation, or provider component to configure before you can use it. Once installed, import the components and composables you need directly from `notform`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"}: ```vue [App.vue] ``` NotForm follows the normal Vue component model: there is no global registration step, and nothing is installed as a Vue plugin. Import only the pieces you actually use — `useNotForm`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} to create the form instance, ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} to provide it to your fields, and ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} / ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} / ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} for the fields themselves. The full breakdown of this example — defining a schema, wiring up individual fields, and displaying validation messages — is covered step by step in [Quickstart](https://notformdocs.vercel.app/docs/getting-started/quickstart). ## Nuxt For Nuxt applications, use the [Nuxt Module](https://notformdocs.vercel.app/docs/getting-started/nuxt-module) rather than manually installing and importing `notform`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} as shown above. The module installs the package for you, registers every component and composable as a Nuxt auto-import, and wires up type support — so you can start writing ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} and `useNotForm`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} directly in your `.vue`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} files without an `import`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} statement. ## Next steps NotForm is now installed and ready to use in your project. Continue to [Quickstart](https://notformdocs.vercel.app/docs/getting-started/quickstart) to build your first form end-to-end: defining a schema, initializing the form, rendering fields, and displaying validation errors. # Quickstart This guide walks you through building a simple login form using the `useNotForm`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} composable and NotForm's headless components. ::tip Prefer experimenting in your browser with zero setup? You can test ideas directly in the [interactive Playground](https://notformdocs.vercel.app/playground). :: Here is the login form we will be building. Try submitting the form empty, or typing an invalid email, to see the validation in action. ::code-preview :demos-native-quickstart #code :::code-collapse :docs-demo-code{file="native/quickstart"} ::: :: ## Step-by-step ::steps{level="3"} ### Define the Schema First, define your validation schema using a Standard Schema compliant library (this guide uses `zod`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"}). The schema describes both fields the login form needs — `email`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} and `password`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} — along with the rules each must satisfy. ```ts import { z } from 'zod' const schema = z.object({ email: z.email('Enter a valid email'), password: z.string('Invalid input').min(8, 'At least 8 characters'), }) ``` This schema is the single source of truth for the form: it decides the shape of `form.values`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"}, drives which field paths are valid to use with ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"}, and produces the validation messages you'll render later with ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"}. ### Initialize the Form Use the `useNotForm`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} composable to create your form instance. Pass in the `schema`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} you just defined and an `onSubmit`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} handler that runs once validation succeeds. ```ts const form = useNotForm({ async onSubmit(data) { // `data` is fully typed as { email: string, password: string } console.log('Form submitted:', data) }, schema, }) ``` `form`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} is the reactive object every other piece of this guide plugs into — ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} reads it for context, ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} reads and writes to `form.values`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"}, and ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} reads validation errors from it. ### Render the Form Use the ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} component to wrap your form and pass it the `form`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} instance via the `form`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} prop. ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} renders a native ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} element and automatically prevents the browser's default `submit`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} and `reset`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} behavior, so binding `@submit="form.submit"`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} and `@reset="form.reset()"`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} is all you need — no `event.preventDefault()`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"} required. ```vue ``` ### Connect the Fields Wrap each input in a ``{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="vue"} component. Its default slot exposes two things you'll use on every field: `path`{.shiki,shiki-themes,material-theme-lighter,material-theme,material-theme-palenight lang="ts-type"}, a stable string ID for that field (handy for `