Forms
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.
<UiForm :state="..." />State
requiredstate 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.
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.
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.
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.
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
staterequiredRecord<string, any>FormSchema"submit" | "input"'submit'Slots
defaultEmits
submit[state: Record<string, any>]error[issues: FormIssue[]]Types
interface FormContext {
errors: ComputedRef<Record<string, string>>;
disabled: ComputedRef<boolean>;
}interface FormIssue {
/** Dotted path to the field — `email`, or `address.city` for a nested value. */
name: string;
message: string;
}type FormSchema = FormRules | StandardSchemaLike;