VfForm
Basic
A form with a few elements and a submit button.
<VfForm>
<VfText name="name" label="Name" />
<VfText name="email" label="Email" />
<VfButton name="submit" text="Submit" action="submit" />
</VfForm><template>
<VfForm>
<VfText name="name" label="Name" />
<VfText name="email" label="Email" />
<VfButton name="submit" text="Submit" action="submit" />
</VfForm>
</template><script setup lang="ts">
import { defineSchema, useForm } from '@vueform/ui'
const schema = defineSchema({
name: { type: 'text', label: 'Name' },
email: { type: 'text', label: 'Email' },
submit: { type: 'button', text: 'Submit', action: 'submit' },
})
const [Form] = useForm({ schema })
</script>
<template>
<Form />
</template>Stepped
The stepped prop turns the form into a multi-step wizard with a progress indicator and navigation buttons. Each direct child with a page prop becomes a step. The progress, nav, and back props control the visibility of the progress bar, navigation buttons, and back button respectively. Use nextLabel, backLabel, and finishLabel to customize button text. See Multi-Page Forms for details.
<VfForm stepped hide-errors>
<VfGroup name="personal" page="Personal">
<VfText name="first_name" label="First name" />
<VfText name="last_name" label="Last name" />
</VfGroup>
<VfGroup name="contact" page="Contact">
<VfText name="email" label="Email" />
<VfText name="phone" label="Phone" />
</VfGroup>
<VfGroup name="review" page="Review">
<VfCheckbox name="terms" text="I accept the terms" />
</VfGroup>
</VfForm><template>
<VfForm stepped hide-errors>
<VfGroup name="personal" page="Personal">
<VfText name="first_name" label="First name" />
<VfText name="last_name" label="Last name" />
</VfGroup>
<VfGroup name="contact" page="Contact">
<VfText name="email" label="Email" />
<VfText name="phone" label="Phone" />
</VfGroup>
<VfGroup name="review" page="Review">
<VfCheckbox name="terms" text="I accept the terms" />
</VfGroup>
</VfForm>
</template><script setup lang="ts">
import { defineSchema, useForm } from '@vueform/ui'
const schema = defineSchema({
personal: {
type: 'group',
page: 'Personal',
schema: {
first_name: { type: 'text', label: 'First name' },
last_name: { type: 'text', label: 'Last name' },
},
},
contact: {
type: 'group',
page: 'Contact',
schema: {
email: { type: 'text', label: 'Email' },
phone: { type: 'text', label: 'Phone' },
},
},
review: {
type: 'group',
page: 'Review',
schema: {
terms: {
type: 'checkbox',
text: 'I accept the terms',
},
},
},
})
const [MyForm] = useForm({
schema,
stepped: true,
hideErrors: true,
})
</script>
<template>
<MyForm />
</template>Tabbed
The tabbed prop renders direct children as freely navigable tabs. Each child's page prop sets the tab label. Unlike stepped forms, any tab can be selected at any time. See Multi-Page Forms for details.
<VfForm tabbed>
<VfGroup name="general" page="General">
<VfText name="name" label="Name" />
<VfText name="email" label="Email" />
</VfGroup>
<VfGroup name="profile" page="Profile">
<VfText name="bio" label="Bio" />
<VfText name="website" label="Website" />
</VfGroup>
</VfForm><template>
<VfForm tabbed>
<VfGroup name="general" page="General">
<VfText name="name" label="Name" />
<VfText name="email" label="Email" />
</VfGroup>
<VfGroup name="profile" page="Profile">
<VfText name="bio" label="Bio" />
<VfText name="website" label="Website" />
</VfGroup>
</VfForm>
</template><script setup lang="ts">
import { defineSchema, useForm } from '@vueform/ui'
const schema = defineSchema({
general: {
type: 'group',
page: 'General',
schema: {
name: { type: 'text', label: 'Name' },
email: { type: 'text', label: 'Email' },
},
},
profile: {
type: 'group',
page: 'Profile',
schema: {
bio: { type: 'text', label: 'Bio' },
website: { type: 'text', label: 'Website' },
},
},
})
const [MyForm] = useForm({ schema, tabbed: true })
</script>
<template>
<MyForm />
</template>Custom Tabs
The #tab slot customizes each tab's content while keeping the default tab bar, buttons, and accessibility wiring. It receives the resolved page's state — label, index, selected, invalid, loading, and meta from the page prop's tuple form.
<VfForm tabbed>
<template #tab="{ label, index }">
{{ index + 1 }}. {{ label }}
</template>
<VfGroup name="general" page="General">
<VfText name="name" label="Name" />
</VfGroup>
<VfGroup name="profile" page="Profile">
<VfText name="bio" label="Bio" />
</VfGroup>
</VfForm><template>
<VfForm tabbed>
<template #tab="{ label, index }">
{{ index + 1 }}. {{ label }}
</template>
<VfGroup name="general" page="General">
<VfText name="name" label="Name" />
</VfGroup>
<VfGroup name="profile" page="Profile">
<VfText name="bio" label="Bio" />
</VfGroup>
</VfForm>
</template><script setup lang="ts">
import { h } from 'vue'
import { defineSchema, defineSlot, useForm } from '@vueform/ui'
const schema = defineSchema({
general: {
type: 'group',
page: 'General',
schema: {
name: { type: 'text', label: 'Name' },
},
},
profile: {
type: 'group',
page: 'Profile',
schema: {
bio: { type: 'text', label: 'Bio' },
},
},
})
const [MyForm] = useForm({
schema,
tabbed: true,
slots: {
tab: defineSlot('VfForm.tab', {
props: ['label', 'index'],
setup: (props) => () => `${props.index + 1}. ${props.label}`,
}),
},
})
</script>
<template>
<MyForm />
</template>To replace the entire tab bar, use the #tabs slot instead. It receives the pager — render anything and drive navigation through its pages:
<VfForm tabbed>
<template #tabs="{ pager }">
<div class="custom-tabs">
<button
v-for="page in pager.pages"
:key="page.name"
:class="{ active: page.selected }"
@click="page.select()"
>
{{ page.label }}
</button>
</div>
</template>
<VfGroup name="general" page="General">
<VfText name="name" label="Name" />
</VfGroup>
<VfGroup name="profile" page="Profile">
<VfText name="bio" label="Bio" />
</VfGroup>
</VfForm><template>
<VfForm tabbed>
<template #tabs="{ pager }">
<div class="custom-tabs">
<button
v-for="page in pager.pages"
:key="page.name"
:class="{ active: page.selected }"
@click="page.select()"
>
{{ page.label }}
</button>
</div>
</template>
<VfGroup name="general" page="General">
<VfText name="name" label="Name" />
</VfGroup>
<VfGroup name="profile" page="Profile">
<VfText name="bio" label="Bio" />
</VfGroup>
</VfForm>
</template><script setup lang="ts">
import { h } from 'vue'
import { defineSchema, defineSlot, useForm } from '@vueform/ui'
const schema = defineSchema({
general: {
type: 'group',
page: 'General',
schema: {
name: { type: 'text', label: 'Name' },
},
},
profile: {
type: 'group',
page: 'Profile',
schema: {
bio: { type: 'text', label: 'Bio' },
},
},
})
const [MyForm] = useForm({
schema,
tabbed: true,
slots: {
tabs: defineSlot('VfForm.tabs', {
props: ['pager'],
setup: (props) => () =>
h('div', { class: 'custom-tabs' },
props.pager.pages.map((page) =>
h('button', {
key: page.name,
class: { active: page.selected },
onClick: () => page.select(),
}, page.label)
)
),
}),
},
})
</script>
<template>
<MyForm />
</template>Custom Steps
The stepped counterparts work the same way: #step customizes each indicator inside the default VfSteps wrapper, #steps replaces the whole progress bar (receiving the pager). On top of the tab slot props, #step also receives enabled and completed — here numbering the steps and checking off completed ones:
<VfForm stepped hide-errors>
<template #step="{ label, index, completed }">
{{ completed ? '✓' : index + 1 }}. {{ label }}
</template>
<VfGroup name="personal" page="Personal">
<VfText name="first_name" label="First name" />
</VfGroup>
<VfGroup name="contact" page="Contact">
<VfText name="email" label="Email" />
</VfGroup>
<VfGroup name="review" page="Review">
<VfCheckbox name="terms" text="I accept the terms" />
</VfGroup>
</VfForm><template>
<VfForm stepped hide-errors>
<template #step="{ label, index, completed }">
{{ completed ? '✓' : index + 1 }}. {{ label }}
</template>
<VfGroup name="personal" page="Personal">
<VfText name="first_name" label="First name" />
</VfGroup>
<VfGroup name="contact" page="Contact">
<VfText name="email" label="Email" />
</VfGroup>
<VfGroup name="review" page="Review">
<VfCheckbox name="terms" text="I accept the terms" />
</VfGroup>
</VfForm>
</template><script setup lang="ts">
import { defineSchema, defineSlot, useForm } from '@vueform/ui'
const schema = defineSchema({
personal: {
type: 'group',
page: 'Personal',
schema: {
first_name: { type: 'text', label: 'First name' },
},
},
contact: {
type: 'group',
page: 'Contact',
schema: {
email: { type: 'text', label: 'Email' },
},
},
review: {
type: 'group',
page: 'Review',
schema: {
terms: { type: 'checkbox', text: 'I accept the terms' },
},
},
})
const [MyForm] = useForm({
schema,
stepped: true,
hideErrors: true,
slots: {
step: defineSlot('VfForm.step', {
props: ['label', 'index', 'completed'],
setup: (props) => () =>
`${props.completed ? '✓' : props.index + 1}. ${props.label}`,
}),
},
})
</script>
<template>
<MyForm />
</template>Custom Navigation
Each navigation button has a pair of slots: #back / #next / #finish replace the whole button, while #back-text / #next-text / #finish-text swap only the label inside the default button — keeping its styling, disabled, and loading behavior:
<VfForm stepped hide-errors>
<template #next-text="{ pager }">
Continue ({{ pager.index + 1 }}/{{ pager.pages.length }})
</template>
<template #finish-text>
Complete
</template>
<VfGroup name="personal" page="Personal">
<VfText name="first_name" label="First name" />
</VfGroup>
<VfGroup name="contact" page="Contact">
<VfText name="email" label="Email" />
</VfGroup>
<VfGroup name="review" page="Review">
<VfCheckbox name="terms" text="I accept the terms" />
</VfGroup>
</VfForm><template>
<VfForm stepped hide-errors>
<template #next-text="{ pager }">
Continue ({{ pager.index + 1 }}/{{ pager.pages.length }})
</template>
<template #finish-text>
Complete
</template>
<VfGroup name="personal" page="Personal">
<VfText name="first_name" label="First name" />
</VfGroup>
<VfGroup name="contact" page="Contact">
<VfText name="email" label="Email" />
</VfGroup>
<VfGroup name="review" page="Review">
<VfCheckbox name="terms" text="I accept the terms" />
</VfGroup>
</VfForm>
</template><script setup lang="ts">
import { defineSchema, defineSlot, useForm } from '@vueform/ui'
const schema = defineSchema({
personal: {
type: 'group',
page: 'Personal',
schema: {
first_name: { type: 'text', label: 'First name' },
},
},
contact: {
type: 'group',
page: 'Contact',
schema: {
email: { type: 'text', label: 'Email' },
},
},
review: {
type: 'group',
page: 'Review',
schema: {
terms: { type: 'checkbox', text: 'I accept the terms' },
},
},
})
const [MyForm] = useForm({
schema,
stepped: true,
hideErrors: true,
slots: {
'next-text': defineSlot('VfForm.next-text', {
props: ['pager'],
setup: (props) => () =>
`Continue (${props.pager.index + 1}/${props.pager.pages.length})`,
}),
'finish-text': defineSlot('VfForm.finish-text', {
setup: () => () => 'Complete',
}),
},
})
</script>
<template>
<MyForm />
</template>To replace the entire navigation bar, use the #nav slot — it receives the pager plus the resolved button labels and visibility props:
<VfForm stepped>
<template #nav="{ pager }">
<div class="custom-nav">
<button @click="pager.back()">Back</button>
<button
v-if="pager.nextPage"
@click="pager.next()"
>
Next
</button>
<button v-else @click="pager.finish()">
Submit
</button>
</div>
</template>
<VfGroup name="personal" page="Personal">
<VfText name="first_name" label="First name" />
</VfGroup>
<VfGroup name="contact" page="Contact">
<VfText name="email" label="Email" />
</VfGroup>
</VfForm><template>
<VfForm stepped>
<template #nav="{ pager }">
<div class="custom-nav">
<button @click="pager.back()">Back</button>
<button
v-if="pager.nextPage"
@click="pager.next()"
>
Next
</button>
<button v-else @click="pager.finish()">
Submit
</button>
</div>
</template>
<VfGroup name="personal" page="Personal">
<VfText name="first_name" label="First name" />
</VfGroup>
<VfGroup name="contact" page="Contact">
<VfText name="email" label="Email" />
</VfGroup>
</VfForm>
</template><script setup lang="ts">
import { h } from 'vue'
import { defineSchema, defineSlot, useForm } from '@vueform/ui'
const schema = defineSchema({
personal: {
type: 'group',
page: 'Personal',
schema: {
first_name: { type: 'text', label: 'First name' },
},
},
contact: {
type: 'group',
page: 'Contact',
schema: {
email: { type: 'text', label: 'Email' },
},
},
})
const [MyForm] = useForm({
schema,
stepped: true,
slots: {
nav: defineSlot('VfForm.nav', {
props: ['pager'],
setup: (props) => () =>
h('div', { class: 'custom-nav' }, [
h('button', { onClick: () => props.pager.back() }, 'Back'),
props.pager.nextPage
? h('button', { onClick: () => props.pager.next() }, 'Next')
: h('button', { onClick: () => props.pager.finish() }, 'Submit'),
]),
}),
},
})
</script>
<template>
<MyForm />
</template>Multilingual
The multilingual prop renders a language switcher above the form. The languages prop defines the available languages as a { code: label } map. Elements with translate store per-language values in the model. See Multilingual Inputs for details.
<VfForm
multilingual
:languages="{ en_US: 'English', fr_FR: 'Français' }"
>
<VfText name="title" label="Title" translate />
<VfTextarea name="body" label="Body" translate />
</VfForm><template>
<VfForm
multilingual
:languages="{ en_US: 'English', fr_FR: 'Français' }"
>
<VfText name="title" label="Title" translate />
<VfTextarea name="body" label="Body" translate />
</VfForm>
</template><script setup lang="ts">
import { defineSchema, useForm } from '@vueform/ui'
const schema = defineSchema({
title: {
type: 'text',
label: 'Title',
translate: true,
},
body: {
type: 'textarea',
label: 'Body',
translate: true,
},
})
const [MyForm] = useForm({
schema,
multilingual: true,
languages: { en_US: 'English', fr_FR: 'Français' },
})
</script>
<template>
<MyForm />
</template>Props
Configure the component's behavior and appearance with these props.
General
| Prop | Default | Description |
|---|---|---|
name | — | Optional name identifying this form instance in DevTools and debugging. |
schema | — | The schema defining the form's elements and structure. |
Layout
| Prop | Default | Description |
|---|---|---|
loading | false | Whether the form is in a loading state. |
stacked | false | Whether to render child elements in a stacked (wrapped row) layout. |
Component
| Prop | Default | Description |
|---|---|---|
multilingual | false | Whether the form supports multilingual content. |
validationSchema | — | A Standard Schema (Zod, Valibot, ArkType, …) validated against the whole form data for form-level (cross-field) validation. Issues are bucketed by path onto the matching element's validation state; unmatched / pathless issues surface as the form's root-level errors. Overrides a Standard Schema attached via defineSchema's second argument. |
clearCustomErrors | true | Whether to clear custom/server errors on revalidation. |
errorLevel | 'all' | Controls where validation errors are displayed (element-level, form-level, or both). |
stepped | false | Whether the form uses a multi-step wizard layout. |
progress | true | Whether to show a progress bar in stepped forms. |
nav | true | Whether to show step navigation in stepped forms. |
navPos | 'auto' | The alignment of the step navigation buttons: 'left', 'center', 'right', 'fluid' (stretched to full width), or 'auto'. |
syncSteps | true | Whether to automatically sync step state when data is loaded or the form mounts with pre-filled data. When enabled, steps are enabled up to the last step that has data and that step is selected. |
history | false | Whether to enable browser history for stepped forms. |
routerQueryParam | — | Overrides the global config.routerQueryParam for this form. When set, step names are encoded as a query parameter instead of a path segment. |
back | true | Whether to show the back button in stepped forms. |
nextLabel | — | The label for the next step button. |
backLabel | — | The label for the back step button. |
finishLabel | — | The label for the finish/submit button in stepped forms. |
scrollTopOn | ['always'] | Events that trigger scrolling to the top of the form. |
scrollToError | true | Whether to scroll to the first element with an error on validation failure. |
scrollOptions | { behavior: 'smooth' } | Options passed to scrollIntoView when scrolling. |
focusError | true | Whether to focus the first element with an error on validation failure. |
autosave | — | The remote autosave configuration: an endpoint URL, or an object with an endpoint (URL or handler function) and its scheduling options (on, interval, debounce, invalid, method, transform). |
draft | — | The draft configuration: true, or an object with key, storage, on, debounce, restore, discardOnSuccess, encrypt and sensitive. Drafts save the model to storage on the configured on events, restore it on mount, and are discarded on successful submit. |
submits | false | Whether the form should display a default submit button |
disabled | — | Whether the form is disabled. Accepts a boolean, a ref, or a conditional prop. |
readonly | — | Whether the form is in read-only mode. Accepts a boolean, a ref, or a conditional prop. |
redirect | — | URL to redirect to after successful submission. |
successMessage | — | Message displayed after successful form submission. |
resetOnSuccess | false | Whether to reset the form after successful submission. |
clearOnSuccess | false | Whether to clear the form after successful submission. |
noExpressions | false | Disables expression resolution for the form and every element in it. Elements cannot override it with their own noExpressions: false. |
consts | — | Form-level expression constants. Each value is a TProp that is resolved and merged into the expression evaluation scope. Names already registered in config.$expression.consts keep the config value — the form-level entry is ignored. |
dark | — | Forces the form into dark or light mode regardless of the environment. Requires build.scopedDarkMode: true in the createVueform config. |
tabbed | false | Whether to render the form in a tabbed layout. |
method | 'POST' | The HTTP method for form submission (e.g. POST, PUT). |
endpoint | — | The URL or async function handling form submission. |
setInvalid | false | Whether to set validation errors on elements from a 422 server response. |
setResponse | false | Whether to update form data with the server response data. |
json | false | Whether to send form data as JSON instead of FormData. |
base64 | false | Whether to encode file uploads as base64. |
headers | — | Custom HTTP headers to include in the submission request. |
transformServerInvalid | 'defaultTransformServerInvalid' | Transforms the server's validation error response. |
transformResponse | 'defaultTransformResponse' | Transforms the server response data when setResponse is enabled. |
I18n
| Prop | Default | Description |
|---|---|---|
language | — | The default selected language. |
languages | — | Available languages for multilingual forms. |
Data
| Prop | Default | Description |
|---|---|---|
modelValue | — | The form data object (v-model), or a useModel instance whose store the form attaches (shared data, no cloning or v-model sync). |
transformPayload | — | Transforms form data before submission. |
Validation
| Prop | Default | Description |
|---|---|---|
validateOn | ['blur', 'next'] | Events that trigger form validation (e.g. blur, change, submit). |
ruleMessages | — | Custom validation error messages keyed by rule name. |
hideErrors | false | Whether to hide form-level validation error messages. |
showRequired | ['label'] | Where to show required field indicators (e.g. label, placeholder). |
Conditions
| Prop | Default | Description |
|---|---|---|
conditionsTrigger | — | The source that triggers re-evaluation of the form's async conditional props. Accepts an array of element paths whose values are watched, a Ref, or a function used as a watch getter. When omitted, any change in the form model re-triggers evaluation. |
conditionsDebounce | — | The debounce delay in milliseconds applied before re-evaluating the form's async conditional props when the trigger source changes. Overrides the global $conditions.debounce config (default 300). |
Customization
| Prop | Default | Description |
|---|---|---|
parts | — | Custom DOM attribute overrides for the component's structural parts. |
class | — | CSS classes to apply to the component's root element. |
classes | — | Custom CSS class overrides for the component's structural parts. |
reclass | — | CSS class overrides that replace (rather than merge with) default classes. |
tokens | — | Token overrides applied to the form's CSS custom properties. |
slots | — | Custom Vue components to use for named slots. Every component resolves it through the standard outlets pipeline (see useOutlets). |
presets | — | Named configuration presets to apply to the component. |
template | — | A custom Vue component to use as the template instead of the default. |
component | — | A custom Vue component to render in the component's default slot. |
Context
Reactive properties and methods accessible on the component's Context object.
General
| Name | Description |
|---|---|
name | Optional name identifying this form instance in DevTools and debugging. |
props | The resolved component props with defaults applied. |
Form
| Name | Description |
|---|---|
errorsId | The DOM id for the form's error container element. |
messagesId | The DOM id for the form's messages container element. |
tabs | Pager instance managing tab-based navigation and state. |
steps | Pager instance managing multi-step wizard navigation and state. |
submits | The number of times the form has been submitted. |
pager | The currently active pager (steps or tabs), or undefined if neither is active. |
result | The result object from the most recent submission attempt. |
response | The HTTP response from the most recent submission. |
submit() | Validates and submits the form, returning a result describing the outcome. |
abort() | Aborts the current in-flight submission request. |
handleSubmit() | Handles form submission triggered from the UI. |
config | The resolved global configuration object. |
validationDisabled | Whether submit-time validation is currently disabled (see disableValidation / enableValidation). |
disableValidation() | Turns off the form's built-in submit-time validation entirely — the floor that otherwise always runs regardless of validateOn. Deliberately a programmatic call rather than a prop: opting out of validation should be an explicit act in code, not a template flag. enableValidation turns it back on. |
enableValidation() | Re-enables the form's submit-time validation after disableValidation. |
index | The schema index describing every data-carrying element: the attached instance's index, or an empty one for internally created stores. |
resolvedConsts | Resolved form-level expression constants. Each TProp value is resolved to a primitive and made available to the expression evaluation scope. |
validator | The validator instance the element is registered with: the form's validator, a store-attached one, or a newly created one. |
issues | The form's current validation issues as reported by the validator. |
toStandardSchema() | Exports the form's resolved validation as a Standard Schema. |
syncSteps() | Synchronizes step states based on which steps contain data, enabling steps up to the last one with values. |
autosave() | Performs a remote autosave immediately (when autosave is configured). The validity gate applies as configured; pass invalid to override the config's invalid option for this one call — true sends even while the form is invalid, false enforces the gate. A gated call while the form is invalid defers the save: it runs as soon as the form becomes valid. |
abortAutosave() | Aborts the autosave that's underway or queued — the in-flight request, a running change debounce, and a deferred gated save. Everything settles silently, firing no fail event. Autosaving itself stays on — the triggers and the interval keep arming future saves. |
saveDraft() | Saves the current model snapshot to storage immediately, bypassing the debounce (async because a configured $security.encrypt may be). Unlike the automatic saves, a manual call fires no saveDraft pre-hook — calling it is the consent (draftSaved still fires). Resolves whether a draft was written — false when drafts are inactive, or the write was superseded or failed. |
getDraft() | Resolves the stored draft data without loading it into the form, or undefined when there's nothing (valid) stored (async because a configured $security.decrypt may be). |
restoreDraft() | Restores the stored draft into the form, resolving whether a draft was found and restored (async because a configured $security.decrypt may be). Unlike the automatic restore on mount, a manual call fires no restoreDraft pre-hook — calling it is the consent. |
discardDraft() | Removes the stored draft, if any. Unlike the automatic discard on successful submit, a manual call fires no discardDraft pre-hook — calling it is the consent. |
toJSON() | Serializes the current model value to a JSON string. |
toFormData() | Serializes the current model value to a FormData instance. |
hasAttachedModel | Whether the form attached a useModel instance's store (passed via v-model / the modelValue prop). |
scrollToTop() | Scrolls the viewport to the top of the form element. |
scrollToFirstError() | Scrolls the viewport to the first element that has a validation error. |
focusFirstError() | Focuses the first element that has a validation error and optionally scrolls to it. |
Data
| Name | Description |
|---|---|
model | The reactive model object holding all element values. |
data | The full model data — an alias of model. |
payload | The element's submission payload. Container elements aggregate from children; leaf elements return their own value. |
initialModel | A snapshot of the model at creation time (re-snapshotted by clean()), used for dirty checking and reset. |
update() | Updates the element's model value. Container elements perform a deep merge; leaf elements set the value directly. |
load() | Loads data into the element and fires the load event, cascading to all children. |
reset() | Resets the element to its initial model state and fires the reset event. |
clear() | Clears the element to its null value and fires the clear event. |
State
| Name | Description |
|---|---|
disabled | Whether the form is disabled, resolved from a boolean, ref, or conditional function prop. |
readonly | Whether the form is in read-only mode. |
dirty | Whether the current model value differs from the initial model value. |
submitting | Whether the form is currently being submitted. |
loading | Whether the form is in a loading state (explicit prop or busy children). |
empty | Whether the element's current value is empty. Container elements check whether all children are empty. |
busy | Whether the element is busy, meaning it is either debouncing or has pending async validation. |
Validation
| Name | Description |
|---|---|
invalid | Whether any of the element's validations (across all languages) are currently invalid, including children. |
invalidLanguages | Set of language codes that have validation errors across any translatable element in the form. |
validated | Whether all of the element's validations (across all languages) have been executed, including children. |
validating | Whether the form is currently being validated. |
completed | Whether the element is completed, meaning it either has no rules or has been validated without errors. |
pending | Whether any of the element's validations are currently pending an async check, including children. |
debouncing | Whether any of the element's validations are currently debouncing, including children. |
errors | Validation errors for all elements, keyed by element path, plus the form's own root-level errors under the '' key (e.g. unmatched Standard Schema issues). |
hasError | Whether the element has any active error messages. |
ruleMessages | Custom validation error messages keyed by rule name. |
messages | The list of custom messages from the message bag, including messages from hoisted children. |
messageBag | The element's message bag for custom errors and messages. |
validate() | Runs validation on the element's own rules and all children, returning whether everything is valid. |
invalidate() | Marks all validation instances as invalidated, requiring revalidation. |
unvalidate() | Resets all validation state, including children's. |
clean() | Marks the element as clean by updating the initial model snapshot. |
clearErrors() | Clears all error messages from the message bag. |
clearMessages() | Clears all custom messages from the message bag. |
clearMessageBag() | Clears the entire message bag, removing both errors and messages. |
Layout
| Name | Description |
|---|---|
classes | The resolved CSS class map for the component. |
parts | The resolved DOM attribute overrides for internal elements. |
tokenClass | A unique CSS class scoped to this component instance, used to target the injected token style rule. |
tokenCss | The CSS text for scoped token custom properties on this component instance — a base rule plus a browser-resolved dark-mode block, so the text is identical between server and client renders. |
nonce | Unique security nonce for injecting scoped styles. |
showErrors | Whether form-level validation errors should be displayed. |
Tree
| Name | Description |
|---|---|
elements$ | Map of all registered form elements keyed by their path. |
children$ | Map of all registered child components keyed by their path. |
children | Registry tracking all child components of the form. flat: true also maintains elements$ — every descendant keyed by full path. |
resolvedSchema | The resolved and normalized schema used to render child elements. |
registerChild() | Registers a child element with the form, setting up its model and data path tracking. |
unregisterChild() | Unregisters a child element from the form, cleaning up its model and data path tracking. |
updateChild() | Updates a child element's value in the root model by its data path. |
find() | Finds a registered element by its path or data path. When the form attached a useModel instance, unmounted schema elements resolve to their virtual contexts as a last resort. |
findByDataPath() | Finds a registered element by its data path. |
findSiblings() | Returns all sibling elements that share the same parent path. |
get() | Gets the value of an element by path, falling back to the raw model value. When the path contains a @ separator (e.g. name@label or name@nested.prop), returns the specified property from the element context instead of its model value. |
set() | Sets the value of an element by path, or updates the raw model if the element is not found. |
Events
| Name | Description |
|---|---|
fire() | Fires a form event, mirrored into the attached instance's emitter. |
fireAsync() | Fires a form event asynchronously across both emitters: the form's listeners run first, then the instance's; either side returning false cancels, and the form's result wins when both produce one. |
on() | Subscribes a callback to a specific event. |
off() | Removes a previously registered event callback. |
onBefore() | Registers a callback that runs before the main event listeners. |
onAfter() | Registers a callback that runs after the main event listeners. |
Internationalization
| Name | Description |
|---|---|
language | The currently active language in multilingual forms. |
languages | The localized map of available languages for multilingual forms. |
selectLanguage() | Sets the active language for multilingual forms. |
Handlers
| Name | Description |
|---|---|
handleLanguageSelect() | Handles language selection from the UI. |
Accessibility
| Name | Description |
|---|---|
uid | Unique identifier for this form instance, used for accessibility attributes. |
Events
Events emitted by the component. See Events for details.
| Name | Params | Description |
|---|---|---|
step | newPage, oldPage, pager, context$ | Emitted when the active step changes. |
back | newPage, oldPage, pager, context$ | Emitted when navigating to the previous step. |
next | newPage, oldPage, pager, context$ | Emitted when navigating to the next step. |
beforeStep | newPage, oldPage, pager, context$ | Emitted before the active step changes, allowing cancellation. |
beforeBack | newPage, oldPage, pager, context$ | Emitted before navigating to the previous step, allowing cancellation. |
beforeNext | newPage, oldPage, pager, context$ | Emitted before navigating to the next step, allowing cancellation. |
beforeFinish | pager, context$ | Emitted before the form finishes (submits from the last step), allowing cancellation. |
saveDraft | data, context$ | Emitted before a draft is written to storage. Return false to cancel the write. |
draftSaved | data, context$ | Emitted after a draft was written to storage. |
restoreDraft | data, context$ | Emitted before a stored draft is restored into the form on mount. Return false to skip the restore (the stored draft is kept). |
draftRestored | data, context$ | Emitted after a stored draft was restored into the form, by the automatic restore on mount or a manual restoreDraft() call. |
discardDraft | context$ | Emitted before the stored draft is discarded on successful submit. Return false to keep the draft. |
draftDiscarded | context$ | Emitted after the stored draft was removed. |
autosave | payload, context$ | Emitted before the autosave request is sent. Return false to cancel, a string to use as the endpoint URL, or an HttpResponse to use as the result without making a request. |
autosaveResponse | response, context$ | Emitted whenever an autosave receives an HTTP response, successful or not. |
autosaveSuccess | result, context$ | Emitted when an autosave's response has a 2xx status. |
autosaveFail | result, context$ | Emitted when an autosave fails — a request error, or a response with a non-2xx status. |
tab | newPage, oldPage, pager, context$ | Emitted when the active tab changes. |
beforeTab | newPage, oldPage, pager, context$ | Emitted before the active tab changes, allowing cancellation. |
submit | payload, context$ | Emitted when the form is submitted, before the HTTP request is sent. |
response | response | Emitted when an HTTP response is received from the server. |
fail | result | Emitted when form submission fails due to a network or server error. |
clientInvalid | result | Emitted when a submission attempt fails client-side validation, before any request is made. |
serverInvalid | result | Emitted when form submission fails due to server-side validation errors (a 422 response). |
success | result | Emitted when form submission succeeds. |
canceled | result | Emitted when form submission is canceled before sending a request (e.g. the form is invalid, loading, disabled, already submitting, or a submit handler / endpoint function returned false). |
change | newValue, oldValue, context$ | Emitted when any form element's value changes, with the new and previous values. |
update:modelValue | value | Emitted to update the v-model binding. |
load | value, context$ | Emitted when form data is loaded programmatically via the load method. |
reset | context$ | Emitted when the form is reset to its default values. |
clear | context$ | Emitted when the form is cleared. |
invalid | context$ | Emitted when the form becomes invalid due to client-side validation errors. |
valid | context$ | Emitted when the form passes validation. |
beforeMount | context$ | Emitted before the component is mounted. |
mounted | context$ | Emitted after the component is mounted. |
beforeUnmount | context$ | Emitted before the component is unmounted. |
unmounted | context$ | Emitted after the component is unmounted. |
updated | context$ | Emitted after the component is updated. |
Slots
Named Slots for replacing parts of the component's rendering.
| Name | Scope | Description |
|---|---|---|
template | context | Slot that replaces the entire component rendering. Receives the full component context as a single context prop. |
tabs | pager bindings | Slot that replaces the tabs navigation bar. Receives the pager instance. |
tab | index label meta selected invalid loading el$ bindings | Slot that replaces each individual tab. |
content | Context | Slot that replaces the entire form content area. Receives the form's context as a single context prop, like the template slot. |
languages | invalidLanguages languages language select bindings | Slot that replaces the language selector. Receives available languages and select handler. |
language | language languages value label | Slot that replaces each individual language option. |
steps | pager bindings | Slot that replaces the steps navigation bar. Receives the pager instance. |
step | index label meta selected invalid loading enabled completed el$ bindings | Slot that replaces each individual step indicator. |
error | error path | Slot that replaces each individual error message. |
message | message | Slot that replaces each individual message. |
elements | resolvedSchema | Slot that replaces the form elements area. Receives the resolved schema. |
default | resolvedSchema | Slot for the default form body content. Receives the resolved schema. |
nav | pager next back finish hideBack position | Slot that replaces the navigation bar. Receives pager, button labels, and visibility. |
back | label pager form$ disabled fluid bindings back | Slot that replaces the back button in the navigation bar. |
back-text | label pager form$ | Slot that replaces the back button's text label. |
next | label pager form$ disabled loading fluid bindings next | Slot that replaces the next button in the navigation bar. |
next-text | label pager form$ | Slot that replaces the next button's text label. |
finish | label pager form$ disabled loading fluid bindings finish | Slot that replaces the finish button in the navigation bar. |
finish-text | label pager form$ | Slot that replaces the finish button's text label. |
errors | errors id bindings | Slot that replaces the form-level errors container. |
messages | messages id bindings | Slot that replaces the form-level messages container. |
Parts
DOM attribute overrides targeting internal elements. See Parts for details.
root
The root form element.
| Element | <div> |
| Classes | .vf-form |
| Bindings | data-stacked |
languages
Props forwarded to the nested VfLanguages component.
| Element | <VfLanguages> |
tabs
Props forwarded to the nested VfTabs component.
| Element | <VfTabs> |
steps
Props forwarded to the nested VfSteps component.
| Element | <VfSteps> |
elements
Props forwarded to the nested VfElements component.
| Element | <VfElements> |
errorList
The error list element.
| Element | <ul> |
| Classes | .vf-form-errors |
messageList
The message list element.
| Element | <ul> |
| Classes | .vf-form-messages |
nav
Props forwarded to the nested VfNav component.
| Element | <VfNav> |
errors
The errors container element.
| Element | <div> |
| Classes | .vf-form-errors-wrapper .sr-only? |
| Bindings | id role aria-live aria-atomic tabindex |
error
Each error item element.
| Element | <li> |
| Classes | .vf-form-error |
messages
The messages container element.
| Element | <div> |
| Classes | .vf-form-messages-wrapper |
| Bindings | id role aria-live aria-atomic tabindex |
message
Each message item element.
| Element | <li> |
| Classes | .vf-form-message |
Tokens
All the CSS variables that control this component's appearance with default values in the default theme. See Themes & Tokens for details.
:root {
/**
* Component tokens
* -------------------------------------------------------------------------- */
--vf-form-errors-bg: color-mix(in srgb, var(--vf-red) 10%, transparent);
--vf-form-errors-border-color: var(--vf-red);
--vf-form-errors-border-style: solid;
--vf-form-errors-border-width: 1px;
--vf-form-errors-color: var(--vf-red);
--vf-form-errors-margin: 0 0 var(--vf-gutter) 0;
--vf-form-errors-padding: 0.5rem 0.75rem;
--vf-form-errors-radius: var(--vf-radius);
--vf-form-messages-bg: color-mix(in srgb, var(--vf-green) 10%, transparent);
--vf-form-messages-border-color: var(--vf-green);
--vf-form-messages-border-style: solid;
--vf-form-messages-border-width: 1px;
--vf-form-messages-color: var(--vf-green);
--vf-form-messages-margin: 0 0 var(--vf-gutter) 0;
--vf-form-messages-padding: 0.5rem 0.75rem;
--vf-form-messages-radius: var(--vf-radius);
}{
/**
* Component tokens
* -------------------------------------------------------------------------- */
formErrorsBg: 'color-mix(in srgb, $red 10%, transparent)',
formErrorsBorderColor: '$red',
formErrorsBorderStyle: 'solid',
formErrorsBorderWidth: '1px',
formErrorsColor: '$red',
formErrorsMargin: '0 0 $gutter 0',
formErrorsPadding: '0.5rem 0.75rem',
formErrorsRadius: '$radius',
formMessagesBg: 'color-mix(in srgb, $green 10%, transparent)',
formMessagesBorderColor: '$green',
formMessagesBorderStyle: 'solid',
formMessagesBorderWidth: '1px',
formMessagesColor: '$green',
formMessagesMargin: '0 0 $gutter 0',
formMessagesPadding: '0.5rem 0.75rem',
formMessagesRadius: '$radius',
}