---
title: "useForm"
description: "Create a typed reactive form from a Standard Schema-compatible object schema. Full configuration and instance reference."
canonical_url: "https://notformdocs.vercel.app/docs/composables/use-form"
---

> Index of every page on this site: [llms.txt](https://notformdocs.vercel.app/llms.txt)

`useForm` creates the form instance that owns values, errors, field instances, and submit state. Call it in a Vue setup context (`<script setup>` or `setup()`), so its timers and watchers are cleaned up when the component unmounts.

```ts
import { useForm } from 'notform'
import { z } from 'zod'

const form = useForm({
  initialValues: { displayName: '', email: '' },
  onSubmit(data) {
    console.log(data)
  },
  schema: z.object({
    displayName: z.string().min(1),
    email: z.email(),
  }),
})
```

In Nuxt, `useForm` is auto-imported. See the [Nuxt module guide](https://notformdocs.vercel.app/docs/getting-started/nuxt-module).

## Configuration

<field-group>
<field name="schema" type="MaybeRefOrGetter<TSchema>" :required="true">

A Standard Schema-compatible **object** schema. May be a value, a ref, or a getter. It is read each time validation runs.

</field>

<field name="initialValues" type="DeepPartial<SchemaInput<TSchema>>">

Starting values and the baseline for `isDirty` and `reset()`. May be partial, but provide a value for every bound field.

</field>

<field name="initialErrors" type="ValidationIssue[]">

Issues shown before the first validation. An issue without a `path` is form-level.

</field>

<field name="onSubmit" type="(data: SchemaOutput<TSchema>) => void | Promise<void>">

Called after the whole form validates. The argument is the parsed schema **output**.

</field>
</field-group>

## Returned instance

The returned object is reactive. Read members directly, for example `form.isDirty`.

### State

<table>
<thead>
  <tr>
    <th>
      Member
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        values
      </code>
    </td>
    
    <td>
      <code>
        SchemaInput<TSchema>
      </code>
    </td>
    
    <td>
      Live input values. Editable directly, through <code>
        setValue
      </code>
      
      , or through field models.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        errors
      </code>
    </td>
    
    <td>
      <code>
        ValidationIssue[]
      </code>
    </td>
    
    <td>
      Current issues. Empty when nothing is wrong.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        isDirty
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Values differ from the baseline.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        isTouched
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      A field blurred or a submit was attempted.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        isSubmitted
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      At least one submit was attempted.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        isSubmitting
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      Validation or <code>
        onSubmit
      </code>
      
       is running.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        isValidating
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      A validation is running.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        isValid
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      The error list is empty. Does not validate.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        submitCount
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      Submit attempts since the last reset.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        resetCount
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      Number of resets.
    </td>
  </tr>
</tbody>
</table>

### Fields

<table>
<thead>
  <tr>
    <th>
      Member
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        field(path, config?)
      </code>
    </td>
    
    <td>
      Get or create the cached field for a typed input path.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        arrayField(path, config?)
      </code>
    </td>
    
    <td>
      Get or create the cached array field for a typed array path.
    </td>
  </tr>
</tbody>
</table>

The first call for a path creates the field and reads `config`. Later calls return the same instance and ignore any config. See [Validation](https://notformdocs.vercel.app/docs/guides/validation#configure-a-field).

### Actions

<table>
<thead>
  <tr>
    <th>
      Member
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        submit(event?)
      </code>
    </td>
    
    <td>
      Validate the form and call <code>
        onSubmit
      </code>
      
       if it passes. Prevents the event's default action. Ignored while a submit is running. Rejects if <code>
        onSubmit
      </code>
      
       throws.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        validate()
      </code>
    </td>
    
    <td>
      Validate the whole form without calling <code>
        onSubmit
      </code>
      
      . Replaces all errors. Resolves with <code>
        { value }
      </code>
      
       or <code>
        { issues }
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        reset(values?, errors?)
      </code>
    </td>
    
    <td>
      Restore the baseline, or replace it when arguments are given. Clears touched and submit state.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        setError(issue)
      </code>
    </td>
    
    <td>
      With a <code>
        path
      </code>
      
      , replaces errors at that path. Without, appends a form-level error.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        setValue(path, value)
      </code>
    </td>
    
    <td>
      Write a typed input value. Does not validate.
    </td>
  </tr>
</tbody>
</table>

## Typed paths and values

Paths use dot notation and are checked against the schema **input**. `form.field('address.city')` targets a nested value, and `form.arrayField('contacts')` targets an array. Values in `form.values` use the input type, and `onSubmit` receives the output type. See [Schema input and output](https://notformdocs.vercel.app/docs/guides/schema-input-output) and [TypeScript](https://notformdocs.vercel.app/docs/guides/typescript).

## Notes

- Call `useForm` once per form. Each call creates an independent form.
- Outside a Vue setup context, cleanup on unmount does not run.
- Changing a reactive `schema` discards in-flight validations but does not re-validate.
- The returned type is `UseFormInstance<TSchema>`, the public contract documented here.

## Related

- [Core concepts](https://notformdocs.vercel.app/docs/getting-started/core-concepts)
- [Form state](https://notformdocs.vercel.app/docs/guides/form-state)
- [Submission and reset](https://notformdocs.vercel.app/docs/guides/submission-and-reset)

## Sitemap

See the full [sitemap](https://notformdocs.vercel.app/sitemap.md) for all pages.
