# Forms

> Form state and validation with zod, valibot or any Standard Schema library.

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

const toast = useToast();
// Not `ref="form"`: that name is taken by the `form` values below.
const formRef = useTemplateRef("signup-form");

const { form, errors, submit, processing } = useForm({
  schema: z.object({
    name: z.string().min(1, "Tell us your name."),
    email: z.email("Enter a valid email address."),
    password: z.string().min(8, "Use at least 8 characters."),
  }),
  initialValues: { name: "", email: "", password: "" },
  formRef,
  onSubmit: async (values) => {
    await new Promise((resolve) => setTimeout(resolve, 800));
    toast.success(`Welcome, ${values.name}!`);
  },
});
</script>

<template>
  <form ref="signup-form" class="demo-form" novalidate @submit="submit">
    <UiInput v-model="form.name" name="name" label="Name" :error="errors.name" />
    <UiInput v-model="form.email" name="email" type="email" label="Email" :error="errors.email" />
    <UiInput
      v-model="form.password"
      name="password"
      type="password"
      label="Password"
      hint="At least 8 characters."
      :error="errors.password"
    />
    <UiButton type="submit" :loading="processing" full-width>
      <template #spinner><span class="spinner" /></template>
      Create account
    </UiButton>
  </form>
</template>
```

## Usage

```ts
import { z } from "zod";

const formRef = useTemplateRef("signup-form");

const { form, errors, submit, processing, isDirty, reset } = useForm({
  schema: z.object({
    email: z.email(),
    password: z.string().min(8),
  }),
  initialValues: { email: "", password: "" },
  formRef,
  onSubmit: (values) => api.signUp(values),
  mapError: (error) => (error instanceof ApiError ? error.fields : undefined),
});
```

Give each field a `name` and its error:

```vue-html
<form ref="signup-form" @submit="submit">
  <UiInput v-model="form.email" name="email" label="Email" :error="errors.email" />
  <UiButton type="submit" :loading="processing">Sign up</UiButton>
</form>
```

## Nested fields

Bind nested values as usual, and name each input by its path. Errors use the same path; array items use their index, like `items.0.name`.

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

const toast = useToast();
const formRef = useTemplateRef("shipping-form");

const { form, errors, submit, processing } = useForm({
  schema: z.object({
    name: z.string().min(1, "Who's it for?"),
    address: z.object({
      street: z.string().min(1, "Enter a street address."),
      city: z.string().min(1, "Enter a city."),
      postcode: z.string().regex(/^\d{5}$/, "Use 5 digits."),
    }),
  }),
  initialValues: { name: "", address: { street: "", city: "", postcode: "" } },
  formRef,
  onSubmit: async (values) => {
    await new Promise((resolve) => setTimeout(resolve, 800));
    toast.success(`Shipping to ${values.address.city}.`);
  },
});
</script>

<template>
  <form ref="shipping-form" class="demo-form" novalidate @submit="submit">
    <UiInput v-model="form.name" name="name" label="Full name" :error="errors.name" />
    <UiInput
      v-model="form.address.street"
      name="address.street"
      label="Street"
      :error="errors['address.street']"
    />
    <div class="demo-form-row">
      <UiInput
        v-model="form.address.city"
        name="address.city"
        label="City"
        :error="errors['address.city']"
      />
      <UiInput
        v-model="form.address.postcode"
        name="address.postcode"
        label="Postcode"
        inputmode="numeric"
        :error="errors['address.postcode']"
      />
    </div>
    <UiButton type="submit" :loading="processing" full-width>
      <template #spinner><span class="spinner" /></template>
      Ship it
    </UiButton>
  </form>
</template>
```

```vue-html
<UiInput
  v-model="form.address.city"
  name="address.city"
  label="City"
  :error="errors['address.city']"
/>
```

## Options

| Option          | Description                                                                     |
| --------------- | ------------------------------------------------------------------------------- |
| `schema`        | Any Standard Schema. Its output type is what `onSubmit` receives.               |
| `initialValues` | Starting values; also the baseline for `isDirty` and `reset()`.                 |
| `onSubmit`      | Called with validated values. Errors it throws go through `mapError`.           |
| `mapError`      | Turns a thrown error into field errors, e.g. a 422 from your API.               |
| `onError`       | Called with any thrown error. Without it, non-field errors rethrow.             |
| `formRef`       | The `<form>`; enables blur/input tracking and focusing the first invalid field. |
| `validateOn`    | When a field's error appears: `"blur"` (default), `"input"` or `"submit"`.      |

## Returns

| Key                         | Description                                                               |
| --------------------------- | ------------------------------------------------------------------------- |
| `form`                      | Reactive values to bind with `v-model`.                                   |
| `errors`                    | Visible errors, keyed by field path (`"address.city"` for nested fields). |
| `submit`                    | Validates, then calls `onSubmit`. Use as the form's `@submit`.            |
| `processing` / `validating` | Submit and validation in flight.                                          |
| `isValid` / `isDirty`       | Schema validity, and whether values differ from the baseline.             |
| `reset()` / `setValues()`   | Restore the baseline, or set new values as the baseline.                  |
| `setErrors()` / `touch()`   | Show server errors, or mark a field touched.                              |

## Plain HTML forms

A field with a `name` is sent when a regular `<form>` submits, just like a native input. A multiple Select or Combobox sends one value per pick; a Switch that's off sends nothing.

```vue-html
<form method="post" action="/settings">
  <UiSelect name="plan" v-model="plan" :options="plans" label="Plan" />
  <UiSwitch name="newsletter" v-model="newsletter" label="Newsletter" />
  <UiButton type="submit">Save</UiButton>
</form>
```

`useForm` uses the same `name` to show a field's error once you leave it.
