Quickstart
Build a complete sign-in form with schema validation, field errors, a submit handler, and reset.
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.
1. Define a schema
The schema describes the values the user edits and the rules they must satisfy.
<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.
<script setup lang="ts">
import { useForm } from 'notform'
const form = useForm({
initialValues: { email: '', password: '' },
async onSubmit(data) {
await signIn(data)
},
schema: signInSchema,
})
</script>
initialValuesare what the inputs show first. See Schema input and output.onSubmitruns only after the whole schema validates.datais the validated output.
3. Bind the fields
form.field(path) creates a field. Field hands it to your template through a slot.
<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 inform.values.v-bind="field.events"spreadsonBlur,onChange, andonInput, which tell NotForm when to validate.FieldErrorshows 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.
<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>
novalidate turns off the browser's built-in constraint validation so your schema controls every message.
What you just built
| Behavior | Why it happens |
|---|---|
| Leaving an empty email field shows an error. | Fields validate on blur and change by default. |
| Typing does not show errors on a pristine field. | Input events only validate after the field already has an error. |
| After an error appears, fixing the value clears it as you type. | Default eager mode re-validates on input once a field has an error. |
Clicking Sign in with invalid values shows every error and does not call onSubmit. | Submit validates the whole form first. |
| The button is disabled while the request runs. | form.isSubmitting stays true until onSubmit settles. |
| Reset restores the starting values and clears errors. | Form calls form.reset() on native reset. |
Next steps
- Learn the model behind this in Core concepts.
- Bind checkboxes, radios, selects, and more with Binding inputs.
- Tune when errors appear in Validation.
- Show errors returned by your API with Server errors.