21

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 to onSubmit are 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. onSubmit receives validated data. How you send it is up to you.
  • Global state. Every useForm call creates an independent form. There is no store to configure.

Packages

PackageUse it for
notformVue 3 and Vite applications. Import components and useForm explicitly.
@notform/nuxtNuxt 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

Suggested reading order

  1. Installation
  2. Quickstart
  3. Core concepts
  4. Binding inputs and Validation
  5. Whatever your form needs next: errors, array fields, or a recipe

Building with an AI coding assistant? See Working with AI.

Copyright © 2026 NotForm
Published under MIT License