Vue API
@inertify/form-vue is a headless Vue 3 package. It manages form behavior and returns state through composables or slots; it does not ship field markup or CSS.
Public entry points
Only these package paths are public:
import {
Form,
createFormRenderer,
useForm,
type FormResource,
} from '@inertify/form-vue'
import {
FormProvider,
FormWizard,
} from '@inertify/form-vue/components'
import {
useFormCombobox,
useFormUploads,
} from '@inertify/form-vue/composables'
The package also exports ./package.json. Do not import source files or build output below these entry points; those modules are implementation details and may change without notice.
The package provides ESM, CommonJS, and TypeScript declaration builds. It requires Vue 3.5 or newer and @inertiajs/vue3 3.6 or newer.
useForm()
function useForm<TData extends Record<string, unknown>>(
resource: MaybeRef<FormResource<TData>>,
options?: UseFormOptions<TData>,
): UseFormApi<TData>
useForm() creates the form engine around Inertia's form helper. Pass a reactive resource when the Inertia prop may be refreshed.
import { toRef } from 'vue'
import { useForm, type FormResource } from '@inertify/form-vue'
const props = defineProps<{ profileForm: FormResource }>()
const form = useForm(toRef(props, 'profileForm'), {
propKey: 'profileForm',
})
Options
| Option | Purpose |
|---|---|
syncResourceData | Synchronizes changed server resource data into local state. Defaults to enabled; set to false when the application owns that lifecycle. |
validationTransport | Replaces the default Laravel Precognition request transport. |
uploadTransport | Replaces the default package upload transport. |
submit | Supplies default Inertia visit options and lifecycle callbacks. |
propKey | Identifies the owning Inertia prop for query-backed combobox partial reloads. The default assumption is form. |
submit accepts action/method overrides, Inertia options such as preserveScroll, preserveState, replace, headers, only, except, and reset, plus onBefore, onStart, onProgress, onSuccess, onError, onCancel, and onFinish.
Engine state
UseFormApi exposes computed state:
formId,resource,inertia,data, androotDatafieldsets,fields,visibleFieldsets, andvisibleFieldserrors,rootErrors,hasErrors, andfirstErrorPathprocessing,progress,wasSuccessful, andrecentlySuccessfulisDirty,touched, andshouldWarnOnUnsavedChangesfieldElementsfor registered application-owned controls
Resolved collection children are FormFieldInstance objects. In addition to the schema field, an instance has path, schemaName, schemaField, rowPath, fieldsetIndex, and inherited visibility data. Use path as the input name and error key.
Engine operations
| Group | Methods |
|---|---|
| Values | getValue(), getDefaultValue(), setValue(), setData(), setValues() |
| Touched state | touch(), isTouched() |
| Elements | registerFieldElement() |
| Reset and clearing | reset(), resetField(), clearField(), defaults() |
| Errors | setError(), clearErrors() |
| Submission | transform(), submit(), cancel() |
| Schema lookup | resolveField(), getField(), resolveFieldset() |
| Visibility | isVisible(), isFieldsetVisible() |
| Precognition | validate(path) |
submit() returns false when no action is configured or a submission is already processing. A successful visit makes current data the new defaults and clears touched state. When scrollToFirstError is enabled, the first errored element registered by the application is scrolled into view.
When unsavedWarning is enabled, dirty forms install an Inertia navigation guard in the browser. Set resource.meta.unsavedWarningMessage to customize its confirmation text.
Context
Form, FormProvider, and Wizard provide the engine to descendants. Composables and renderless components use that context when no explicit form argument/prop is given.
The public context helpers are:
FormContextKey
provideFormContext(form)
tryUseFormContext()
useFormContext()
useFormContext() throws outside a provider. tryUseFormContext() returns null instead.
Components
Form and Wizard render one native <form> element. Every other package component returns consumer slot nodes or null and inserts no wrapper element.
Form and FormProvider
Both accept:
form: FormResource— requiredoptions: UseFormOptions— optional
They expose the complete UseFormApi and emit before, start, progress, success, error, cancel, and finish around submission callbacks.
The default slot receives:
{
form, formId, data, rootData, errors, rootErrors,
processing, isDirty, setData, validate, submit, cancel,
reset, defaults, transform, clearErrors
}
Form is the normal root. It renders:
<form id="{formId}" action="{resource.action}" method="get|post" novalidate>
method is the native equivalent of the serialized method, and put, patch, and delete also emit a hidden _method field. novalidate is set because validation is server-driven. The submit handler calls preventDefault() and submits through Inertia, forwarding event.submitter so a named submit button contributes its value. Fallthrough attributes are merged last, so id, action, method, novalidate, class, enctype, and additional @submit listeners can all be supplied per usage. A component ref exposes the element as element alongside the form API.
FormProvider is the same component without the element. Use it when the <form> belongs elsewhere in the layout, when a provider must nest inside another form, or when a page only reads and validates.
<Form :form="resource" v-slot="{ form, processing }">
<AppFormFields :form="form" />
<button :disabled="processing">Save</button>
</Form>
<form> inside Form or Wizard. Nested forms are invalid HTML: the parser discards the inner element together with its submit handler, and submits fall back to a full page navigation.Wizard
Wizard accepts a form resource and options, creates both the form engine and wizard controller, provides the form context, renders the same <form> element as Form, and exposes { form, wizard } to its default slot and { form, wizard, element } on a component ref.
Context consumers
| Component | Props | Default-slot payload |
|---|---|---|
FormFieldsets | form?, only?, except?, includeHidden? | One call per selected fieldset with fieldset, form, id, display index, originalIndex, resolved fields, and visible. Named insertion and replacement slots are also supported. |
FormErrors | form? | form, error map, { name, message }[], hasErrors, firstErrorPath, clearErrors. |
FormSubmit | form? | Submission state plus canSubmit, submit, and cancel. |
FormWizard | form? | The wizard controller and unwrapped step/navigation state and methods. |
FormUploads | form?, required name | Upload controller, field state/value, and path-bound upload, remove, reorder, cancel, retry, pause, resume, and clear methods. |
FormCollection | form?, required name | Collection controller, rows, stable keys, field, bounds, and mutation methods. |
FormFieldsets slot precedence is {id}-fieldset, fieldset-{id}, then default; insertion slots are before-{id}-fieldset and after-{id}-fieldset.
only and except each accept an exact, case-sensitive fieldset ID as string or readonly string[]. only narrows the candidates first and except then removes matches, so exclusion wins when an ID appears in both. Selector array order does not reorder the result; fieldsets remain in schema order. Selection and visibility are independent: includeHidden still controls whether hidden fieldsets and fields are emitted, but never bypasses either selector.
Assign selectable fieldsets explicit, unique PHP IDs with Fieldset::id() because generated fallback IDs are based on schema position. For a fixed application layout, render a known group directly with the generated <FormFields fieldset="identity" /> instead.
createFormRenderer()
Register application-owned field components once while retaining page-level slots:
import {
createFormRenderer,
type FormFieldRenderer,
} from '@inertify/form-vue'
import CheckboxField from './CheckboxField.vue'
import TextField from './TextField.vue'
import UnsupportedField from './UnsupportedField.vue'
const renderers = {
Text: TextField,
Textarea: {
component: TextField,
props: { multiline: true },
},
Checkbox: CheckboxField,
Submit: null,
} satisfies Record<string, FormFieldRenderer>
export const { FormField, FormFields } = createFormRenderer({
name: 'AppForm',
renderers,
fallback: UnsupportedField,
})
CreateFormRendererOptions contains:
| Option | Contract |
|---|---|
renderers | Map of exact serialized component discriminators to a Vue component, { component, props }, or null. These frontend protocol keys are independent of PHP class names. |
fallback | Renderer used for an unregistered component. |
name | Prefix for the generated Vue component names; defaults to RegisteredForm. |
Each factory call snapshots an isolated registry, which makes it safe to create at module scope during SSR. Preset props are merged first; the behavioral field payload remains authoritative. null intentionally renders no field.
The returned FormFields accepts form?, fields?, fieldset?, and includeHidden?. It renders declared layout fields in schema order. A collection renderer owns its repeater or block descendants so nested controls are not emitted twice.
The returned FormField accepts required name (a qualified path or field object), form?, and includeHidden? for explicit placement:
<div class="grid gap-6 md:grid-cols-2">
<FormField name="name" />
<FormField name="email" />
</div>
Field slots
For each resolved field, override resolution follows this order:
field-{path}type-{component}with the component normalized to kebab casedefault{name}-field, the deprecated compatibility fallback- The registered renderer, unsupported handler, or fallback
Insertion slots are before-{path}-field and after-{path}-field. Qualified paths include collection indexes, for example projects.0.title.
Every field slot and registered renderer receives FormFieldSlotProps:
{
field, form, context, controller, name, value,
error, errors, visible, touched, dirty,
disabled, readonly, required,
setValue, blur, validate, registerElement
}
Unknown fields render null unless fallback or an unsupported slot is provided. Components that declare only part of the payload should use defineOptions({ inheritAttrs: false }) so controller objects and callbacks do not fall through to a root DOM element.
Core composables
All composables except useForm() accept an optional UseFormApi; without it, they use provider context.
| Composable | Main contract |
|---|---|
useFormField(fieldOrPath, form?) | One field's writable value, errors, visibility, dirty/touched/semantic state, element registration, blur validation, reset, clear, and clear-errors operations. |
useFormFields(form?) | Resolved schema collections, lookup, and cached field controllers. |
useFormValidation(form?) | Pending state, validate(), validateMany(), debounced validation, cancellation, and error clearing. |
useFormVisibility(form?) | Visible/hidden fields and fieldsets plus visibility predicates. |
useFormSubmission(form?) | Submission state, canSubmit, submit, and cancel. |
useFormWizard(form?) | Steps, current position, completion, labels, guards, goTo(), next(), previous(), validateCurrent(), and reset(). |
useFormUploads(form?) | Upload state by path plus upload, retry, pause/resume, remove, reorder, cancel, and clear. |
useFormCollection(path, form?) | One repeater/blocks collection controller. |
useFormCollections(form?) | Cached collection controllers through forField(path). |
useFormCombobox(path, form?, options?) | Search, paging, selected hydration, record creation, loading/error state, and cancellation. |
useFormCollection() exposes items, stable keys, canAppend, append(), prepend(), insert(), update(), remove(), move(), swap(), duplicate(), appendBlock(), and clear().
useFormCombobox() accepts a custom transport. Its default transport understands endpoint-backed fields and query-backed Inertia option metadata. normalizeComboboxPage() is public for custom transport implementations.
Field controllers
Controller composables adapt serialized behavior to application renderers:
| Controller | Specialized state and operations |
|---|---|
useTextInput() | Raw/text/display values, masking, numeric formatting, parsing, and input handling. |
useSlug() | Text behavior plus source tracking, locking, generation, and manual input. |
useChoices() | Selected list plus select, deselect, toggle, and membership checks. |
useDate() | Writable date string, parsed Date, and setDate(). |
useFile() | Field upload state and path-bound upload operations. |
useRichText() | HTML, companion image tokens, image upload state, and image operations. |
useComposer() | Text, attachment state, and attachment upload operations. |
useOtp() | Length, digit list, completeness, and setDigit(). |
useLink() | Plain/structured URL parts, safe-scheme state, normalized href, target, and setters. |
useFormFieldController() | Chooses a built-in controller from the serialized component discriminator, falling back to useFormField(). |
The aliases useTextInputField, useSlugField, useChoicesField, useDateField, useFileField, useRichTextField, useComposerField, useOtpField, and useLinkField are also public.
Transports
The package exports defaultValidationTransport, defaultUploadTransport, normalizeUploadDescriptor, and defaultComboboxTransport for composition with application-specific transports.
Custom validation receives the action, method, qualified path, current data, resource, and AbortSignal. Custom upload transport receives the resolved field, files, normalized descriptor, progress callback, resumable session state, and signal. Cancellation and stale-response handling remain part of the form engine.
Rendering safety
The package deliberately does not render labels, error relationships, focus styles, dialogs, or editor HTML. Application renderers must provide accessible names, descriptions, error announcements, keyboard behavior, and focus handling.
useLink() normalizes configured schemes for renderer convenience; server validation is authoritative. useRichText() treats the value as HTML state and does not sanitize it. Never use v-html with untrusted content unless the application has sanitized it under an explicit policy.