Reference

Vue API

Public entry points, form engine, renderless components, renderer factory, composables, and controllers.

@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

OptionPurpose
syncResourceDataSynchronizes changed server resource data into local state. Defaults to enabled; set to false when the application owns that lifecycle.
validationTransportReplaces the default Laravel Precognition request transport.
uploadTransportReplaces the default package upload transport.
submitSupplies default Inertia visit options and lifecycle callbacks.
propKeyIdentifies 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, and rootData
  • fieldsets, fields, visibleFieldsets, and visibleFields
  • errors, rootErrors, hasErrors, and firstErrorPath
  • processing, progress, wasSuccessful, and recentlySuccessful
  • isDirty, touched, and shouldWarnOnUnsavedChanges
  • fieldElements for 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

GroupMethods
ValuesgetValue(), getDefaultValue(), setValue(), setData(), setValues()
Touched statetouch(), isTouched()
ElementsregisterFieldElement()
Reset and clearingreset(), resetField(), clearField(), defaults()
ErrorssetError(), clearErrors()
Submissiontransform(), submit(), cancel()
Schema lookupresolveField(), getField(), resolveFieldset()
VisibilityisVisible(), isFieldsetVisible()
Precognitionvalidate(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 — required
  • options: 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>
Do not nest your own <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

ComponentPropsDefault-slot payload
FormFieldsetsform?, 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.
FormErrorsform?form, error map, { name, message }[], hasErrors, firstErrorPath, clearErrors.
FormSubmitform?Submission state plus canSubmit, submit, and cancel.
FormWizardform?The wizard controller and unwrapped step/navigation state and methods.
FormUploadsform?, required nameUpload controller, field state/value, and path-bound upload, remove, reorder, cancel, retry, pause, resume, and clear methods.
FormCollectionform?, required nameCollection 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:

OptionContract
renderersMap of exact serialized component discriminators to a Vue component, { component, props }, or null. These frontend protocol keys are independent of PHP class names.
fallbackRenderer used for an unregistered component.
namePrefix 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:

  1. field-{path}
  2. type-{component} with the component normalized to kebab case
  3. default
  4. {name}-field, the deprecated compatibility fallback
  5. 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.

ComposableMain 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:

ControllerSpecialized 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.

Transport customization does not relax the server contract. Keep authentication, CSRF protection, authorization, tenant scope, file validation, token validation, and rate limits on the Laravel endpoints.

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.

Copyright © 2026 Inertify · Released under the MIT License