Quick start

A typed schema, validated inputs, a submit handler. The minimum viable form in five minutes.

Time
~5 minutes
You'll learn
useForm + register + handleSubmit

Try the form below: clear the password and submit to watch focus pull to the broken field; submit with valid values to see the alert fire. Every behavior on screen comes from the Zod schema in code, which you'll see in the Build a form section next.

Build a form

Hand useForm a Zod schema and the reactive form comes back ready. This page reaches for three properties on the returned form: register for the input binding, handleSubmit for the submit gate, and fields for per-field error reads.

import { useForm } from 'attaform/zod'
import { z } from 'zod'

const schema = z.object({
  email: z.email(),
  password: z.string().min(8),
})

const form = useForm({ schema })

const onSubmit = form.handleSubmit((values) => {
  // values is the parsed Zod output, fully typed.
  alert(JSON.stringify(values, null, 2))
})

Bind inputs to schema paths with v-register:

<template>
  <form @submit="onSubmit">
    <input v-register="form.register('email')" />
    <em v-if="form.fields.email.showErrors">{{ form.fields.email.firstError?.message }}</em>

    <input v-register="form.register('password')" type="password" />
    <em v-if="form.fields.password.showErrors">{{ form.fields.password.firstError?.message }}</em>

    <button type="submit">Sign in</button>
  </form>
</template>

form.register('email') returns what the v-register directive binds to. The directive handles the value read, the write, the coercion, and focus on invalid submit. Errors render via form.fields.<path>.firstError?.message, gated by form.fields.<path>.showErrors so the form doesn't yell on first paint.

What's next