---
title: "Quickstart"
description: "Build a complete sign-in form with schema validation, field errors, a submit handler, and reset."
canonical_url: "https://notformdocs.vercel.app/docs/getting-started/quickstart"
---

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

This page builds a sign-in form from start to finish. You will define a schema, create a form, bind two inputs, and handle submission.

It assumes you have completed [Installation](https://notformdocs.vercel.app/docs/getting-started/installation).

## 1. Define a schema

The schema describes the values the user edits and the rules they must satisfy.

```vue
<script setup lang="ts">
import { z } from 'zod'

const signInSchema = z.object({
  email: z.email('Enter a valid email address.'),
  password: z.string().min(8, 'Use at least 8 characters.'),
})
</script>
```

## 2. Create the form

`useForm` takes the schema, the starting values, and a submit handler.

```vue
<script setup lang="ts">
import { useForm } from 'notform'

const form = useForm({
  initialValues: { email: '', password: '' },
  async onSubmit(data) {
    await signIn(data)
  },
  schema: signInSchema,
})
</script>
```

- `initialValues` are what the inputs show first. See [Schema input and output](https://notformdocs.vercel.app/docs/guides/schema-input-output#initial-values).
- `onSubmit` runs only after the whole schema validates. `data` is the validated output.

## 3. Bind the fields

`form.field(path)` creates a field. `Field` hands it to your template through a slot.

```vue
<template>
  <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"
      autocomplete="email"
      type="email"
    >

    <FieldError :errors="field" />
  </Field>
</template>
```

- `v-model="field.model"` reads and writes the value in `form.values`.
- `v-bind="field.events"` spreads `onBlur`, `onChange`, and `onInput`, which tell NotForm when to validate.
- `FieldError` shows the first error for this field and renders nothing when there is none.

## 4. Render and submit

`Form` renders a native `<form>`. Native submit calls `form.submit`, and native reset calls `form.reset`.

<code-collapse>

```vue
<script setup lang="ts">
import { Field, FieldError, Form, useForm } from 'notform'
import { z } from 'zod'

const signInSchema = z.object({
  email: z.email('Enter a valid email address.'),
  password: z.string().min(8, 'Use at least 8 characters.'),
})

const form = useForm({
  initialValues: { email: '', password: '' },
  async onSubmit(data) {
    await signIn(data)
  },
  schema: signInSchema,
})

async function signIn(data: { email: string, password: string }) {
  await fetch('/api/sign-in', {
    body: JSON.stringify(data),
    headers: { 'content-type': 'application/json' },
    method: 'POST',
  })
}
</script>

<template>
  <Form
    :form="form"
    novalidate
  >
    <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"
        autocomplete="email"
        type="email"
      >

      <FieldError :errors="field" />
    </Field>

    <Field
      v-slot="{ field }"
      :field="form.field('password')"
    >
      <label :for="field.path">Password</label>

      <input
        :id="field.path"
        v-model="field.model"
        v-bind="field.events"
        autocomplete="current-password"
        type="password"
      >

      <FieldError :errors="field" />
    </Field>

    <button
      type="submit"
      :disabled="form.isSubmitting"
    >
      {{ form.isSubmitting ? 'Signing in...' : 'Sign in' }}
    </button>

    <button
      type="reset"
      :disabled="form.isSubmitting"
    >
      Reset
    </button>
  </Form>
</template>
```

</code-collapse>

`novalidate` turns off the browser's built-in constraint validation so your schema controls every message.

## What you just built

<table>
<thead>
  <tr>
    <th>
      Behavior
    </th>
    
    <th>
      Why it happens
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Leaving an empty email field shows an error.
    </td>
    
    <td>
      Fields validate on <strong>
        blur
      </strong>
      
       and <strong>
        change
      </strong>
      
       by default.
    </td>
  </tr>
  
  <tr>
    <td>
      Typing does not show errors on a pristine field.
    </td>
    
    <td>
      <strong>
        Input
      </strong>
      
       events only validate after the field already has an error.
    </td>
  </tr>
  
  <tr>
    <td>
      After an error appears, fixing the value clears it as you type.
    </td>
    
    <td>
      Default <code>
        eager
      </code>
      
       mode re-validates on input once a field has an error.
    </td>
  </tr>
  
  <tr>
    <td>
      Clicking Sign in with invalid values shows every error and does not call <code>
        onSubmit
      </code>
      
      .
    </td>
    
    <td>
      Submit validates the whole form first.
    </td>
  </tr>
  
  <tr>
    <td>
      The button is disabled while the request runs.
    </td>
    
    <td>
      <code>
        form.isSubmitting
      </code>
      
       stays <code>
        true
      </code>
      
       until <code>
        onSubmit
      </code>
      
       settles.
    </td>
  </tr>
  
  <tr>
    <td>
      Reset restores the starting values and clears errors.
    </td>
    
    <td>
      <code>
        Form
      </code>
      
       calls <code>
        form.reset()
      </code>
      
       on native reset.
    </td>
  </tr>
</tbody>
</table>

## Next steps

- Learn the model behind this in [Core concepts](https://notformdocs.vercel.app/docs/getting-started/core-concepts).
- Bind checkboxes, radios, selects, and more with [Binding inputs](https://notformdocs.vercel.app/docs/guides/binding-inputs).
- Tune when errors appear in [Validation](https://notformdocs.vercel.app/docs/guides/validation).
- Show errors returned by your API with [Server errors](https://notformdocs.vercel.app/docs/recipes/server-errors).

## Sitemap

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