Form

The state and validation container the other form pieces sit in: it validates on submit, routes each error to the `FormField` carrying that `name`, and emits `submit` only once everything passes. Takes a rules record or any Standard Schema validator (zod, valibot, arktype) — neither is a dependency.

html
<UiForm :state="..." />

State

required

state is the reactive object holding the field values, and it stays yours: the form reads it and never replaces it. Bind your inputs to its properties with v-model as you already would, give each FormField the name its schema validates, and the matching error finds its way there on its own. submit fires only once everything passes.

At least 8 characters

The form never replaces state — it reads the object you hand it and writes nothing back. That's what lets it sit over any input, including ones this library doesn't ship: if it can v-model a property, it works here.

The <form> renders novalidate. The browser's own bubbles can't be styled, appear on a different schedule, and would contradict the messages here — so they're turned off and this validation is the only one the reader sees.

Schema

schema says how the values are checked. In its simplest form it's a record of rules keyed by field name, built from the auto-imported required, email, minLength, maxLength, min, max, pattern and match — each of which takes an optional message. A rule is handed the whole state as well as the value, which is what lets match compare two fields; a password confirmation can't be written any other way. Omit schema entirely for a form that only needs submit wiring.

Letters, numbers and dashes

schema is either a rules record keyed by field name or anything implementing Standard Schema. zod, valibot and arktype all qualify and none of them are dependencies — the form duck-types the ~standard property, so the package still installs with nothing behind it.

The first failing rule for a field wins, so an input never carries two complaints at once. Order the rules from cheapest and most fundamental to most specific — required() before email(), or an empty field reports a format problem it doesn't have.

Validate on

validateOn decides when the checking runs. Its default, submit, is the kinder one: nothing is flagged until the reader says they're finished, and an error that does appear clears the moment the field is corrected rather than surviving until the next attempt. input validates from the first keystroke — louder, and worth it only where a mistake is expensive to find late.

1024–65535

Slot props — errors: 0, submitted: false

Disabled

disabled switches off every field inside the form at once, which is what you want while a submission is in flight or the record is read-only. It travels down through the form's context, so a FormField doesn't have to pass anything on to the input it wraps.

Class

class lands on the <form> element itself, which is the only layout this component renders — there is no inner wrapper. Every example on this page uses it for the vertical rhythm between fields; a two-column form is the same idea with a grid.

A Standard Schema validator

Anything implementing Standard Schema — zod, valibot, arktype — is accepted as-is, and none of them are dependencies of this package: the form duck-types the ~standard property. Hand it the same schema your API route already validates with and the two can't drift. Nested paths map to dotted names, so address.city addresses the field below.

A FormField's name is the whole wiring. It has to match the key the schema validates, dotted for nested values (address.city), or the error is computed and then has nowhere to go. A field with no name inside a form is just a layout wrapper.

Server errors, and the exposed methods

Some errors only a server knows — that an email is already taken, that a coupon expired. setErrors() puts them on the right fields without inventing a client-side rule that can't actually check them. validate() and reset() are exposed on the same ref; disabled greys the whole form while a request is in flight.

Try taken@example.com

With the default validate-on="submit", re-validation only ever *clears* errors, never adds them. Before the first submit the form stays silent; afterwards an error disappears as soon as the field is corrected. Validating everything on each keystroke would light up fields the reader hasn't reached yet, which is the behaviour people mean when they call a form aggressive.

setErrors() exists because some failures are only knowable server-side — an address already invited, a coupon just expired. Putting those on the field through the same channel keeps them looking like every other error, without inventing a client-side rule that can't actually check the thing.

validate() and reset() are exposed on the component ref, alongside setErrors(). reset() clears errors and the submitted flag but deliberately leaves your values alone — they're yours, and a reset that wiped a half-typed form would be its own bug report.


API Reference

Generated from the component's source — props, slots and emits as the component actually declares them.

Props

Prop
Type
Default
Description
staterequired
Record<string, any>
The reactive object holding the field values. The form reads it and never replaces it — bind your inputs straight to its properties with `v-model` as you already would. Keeping ownership on your side is what lets the form sit over any input, including ones this library doesn't ship.
FormSchema
How the values are checked. Either a record of rules keyed by field name (`{ email: [required(), email()] }`) or anything implementing Standard Schema — a zod, valibot or arktype schema works unchanged, and none of them are dependencies of this package. Omit it for a form that only needs `submit` wiring.
"submit" | "input"
'submit'
When validation runs. - `submit` — on submit only. Errors that appear then clear as soon as the field is corrected, so the form never nags about a field before the reader has claimed to be finished. - `input` — additionally on every change, from the start. Louder; worth it for a form where a mistake is expensive to discover late.
boolean
false
Disables every field inside, for a form that's saving or awaiting something.
string
Classes for the `<form>` element itself.

Slots

Slot
default

Emits

Event
Payload
Description
submit
[state: Record<string, any>]
Every field passed. Carries the state, which is the same object you handed in.
error
[issues: FormIssue[]]
Validation failed. Carries every issue found, in schema order.

Types

ts
interface FormContext {
    errors: ComputedRef<Record<string, string>>;
    disabled: ComputedRef<boolean>;
}
ts
interface FormIssue {
    /** Dotted path to the field — `email`, or `address.city` for a nested value. */
    name: string;
    message: string;
}
ts
type FormSchema = FormRules | StandardSchemaLike;