LiveVue provides comprehensive form handling with server-side validation, nested objects, and dynamic arrays through the useLiveForm composable and related utilities.
Creates a reactive form instance that synchronizes with LiveView’s form state and provides type-safe field access.
function useLiveForm < T extends object >(
form : MaybeRefOrGetter < Form < T >>,
options ?: FormOptions
) : UseLiveFormReturn < T >
Parameters
form
MaybeRefOrGetter<Form<T>>
required
Reactive reference to the form data from LiveView (typically () => props.form)
Configuration options changeEvent
string | null
default: "null"
Event name to send to the server when form values change. Set to null to disable validation events.
Event name to send to the server when form is submitted
Delay in milliseconds before sending change events to reduce server load
Function to transform form data before sending to server
Returns
Whether the form has no validation errors
Whether the form values differ from initial values
Whether any field has been touched or form has been submitted
Whether a validation request is currently pending
Number of times the form has been submitted
The initial form values (read-only)
Show field factory functions
Get a typed field instance for the given path < P extends PathsToStringProps < T >> (
path : P ,
options ?: FieldOptions
): FormField < PathValue < T , P >>
Get an array field instance for managing dynamic lists < P extends PathsToStringProps < T >> (
path : P
): FormFieldArray < U >
Submit the form to the server. Returns a Promise that resolves with the server response. Reset form to initial state
Usage
< script setup >
import { useLiveForm } from 'live_vue'
import type { Form } from 'live_vue'
interface UserForm {
name : string
email : string
age : number
}
interface Props {
form : Form < UserForm >
}
const props = defineProps < Props >()
const form = useLiveForm (() => props . form , {
changeEvent: 'validate' ,
submitEvent: 'submit' ,
debounceInMiliseconds: 300
})
const nameField = form . field ( 'name' )
const emailField = form . field ( 'email' )
const handleSubmit = async () => {
const result = await form . submit ()
console . log ( 'Form submitted:' , result )
}
</ script >
< template >
< form @submit.prevent = "handleSubmit" >
< div >
< label :for = "nameField.inputAttrs.value.id" > Name </ label >
< input v-bind = "nameField.inputAttrs.value" />
< span v-if = "nameField.errorMessage.value" class = "error" >
{{ nameField.errorMessage.value }}
</ span >
</ div >
< div >
< label :for = "emailField.inputAttrs.value.id" > Email </ label >
< input v-bind = "emailField.inputAttrs.value" type = "email" />
< span v-if = "emailField.errorMessage.value" class = "error" >
{{ emailField.errorMessage.value }}
</ span >
</ div >
< button type = "submit" :disabled = "!form.isValid.value || form.isValidating.value" >
{{ form.isValidating.value ? 'Validating...' : 'Submit' }}
</ button >
</ form >
</ template >
Type definitions
A FormField instance represents a single form field with reactive state and input binding helpers. You typically create fields using form.field(path) from useLiveForm.
Properties
The current field value (reactive)
Array of error messages for this field (read-only, from backend)
errorMessage
Readonly<Ref<string | undefined>>
The first error message, or undefined if no errors (read-only)
Whether the field has no validation errors
Whether the field value differs from its initial value
Whether the field has been focused and blurred
Computed attributes to bind to an input element using v-bind. Includes:
value: Current field value
onInput: Input event handler
onBlur: Blur event handler
name: Field path
id: Sanitized field ID
type: Input type (if specified in options)
checked: For checkboxes/radios
aria-invalid: Accessibility attribute
aria-describedby: Links to error message element
Methods
Create a sub-field for nested objects field < K extends keyof T >( key : K , options ?: FieldOptions ) : FormField < T [ K ]>
Create a sub-field array for nested arrays fieldArray < K extends keyof T >( key : K ) : FormFieldArray < U >
Mark the field as touched (called automatically by inputAttrs.onBlur)
Field options
HTML input type - supports any valid input type (“text”, “email”, “number”, “checkbox”, “radio”, etc.)
For checkbox/radio: the value this input represents when selected
Checkbox handling
< script setup >
const form = useLiveForm (() => props . form )
// Single checkbox (boolean)
const agreeField = form . field ( 'agree' , { type: 'checkbox' })
// Multiple checkboxes (array)
const interestsField = form . field ( 'interests' ) // interests: string[]
</ script >
< template >
<!-- Single checkbox -->
< label >
< input v-bind = "agreeField.inputAttrs.value" />
I agree to terms
</ label >
<!-- Multiple checkboxes -->
< label >
< input v-bind = "form.field('interests', { type: 'checkbox', value: 'vue' }).inputAttrs.value" />
Vue.js
</ label >
< label >
< input v-bind = "form.field('interests', { type: 'checkbox', value: 'react' }).inputAttrs.value" />
React
</ label >
</ template >
A FormFieldArray extends FormField with array-specific methods for managing dynamic lists. You create array fields using form.fieldArray(path).
Additional properties
fields
Readonly<Ref<FormField<T>[]>>
Reactive array of field instances for iteration. Each item in the array is a FormField instance.
Array methods
Add a new item to the array. Returns a Promise that resolves when the server validates the change. ( item ?: Partial < T >) => Promise < any >
Remove an item at the specified index. Returns a Promise that resolves when the server validates the change. ( index : number ) => Promise < any >
Move an item from one index to another. Returns a Promise that resolves when the server validates the change. ( from : number , to : number ) => Promise < any >
Field access methods
Get a field for a specific array item or nested path // By index
field ( 0 , options ? ) // Returns FormField for items[0]
// By path
field ( '[0]' , options ? ) // Returns FormField for items[0]
field ( '[0].name' , options ? ) // Returns FormField for items[0].name
Get a nested array field fieldArray ( '[0].tags' ) // Returns FormFieldArray for items[0].tags
Usage
< script setup >
import { useLiveForm } from 'live_vue'
interface TodoForm {
todos : Array <{
text : string
completed : boolean
}>
}
const props = defineProps <{ form : Form < TodoForm > }>()
const form = useLiveForm (() => props . form , {
changeEvent: 'validate' ,
submitEvent: 'save'
})
const todosArray = form . fieldArray ( 'todos' )
const addTodo = () => {
todosArray . add ({ text: '' , completed: false })
}
</ script >
< template >
< div >
< div v-for = "(todoField, index) in todosArray.fields.value" :key = "index" >
< input v-bind = "todoField.field('text').inputAttrs.value" placeholder = "Todo text" />
< input v-bind = "todoField.field('completed', { type: 'checkbox' }).inputAttrs.value" />
< button @click = "todosArray.remove(index)" > Remove </ button >
</ div >
< button @click = "addTodo" > Add Todo </ button >
</ div >
</ template >
Nested arrays
< script setup >
interface FormData {
users : Array <{
name : string
tags : string []
}>
}
const form = useLiveForm (() => props . form )
const usersArray = form . fieldArray ( 'users' )
</ script >
< template >
< div v-for = "(userField, userIndex) in usersArray.fields.value" :key = "userIndex" >
< input v-bind = "userField.field('name').inputAttrs.value" />
<!-- Nested array -->
< div v-for = "(tagField, tagIndex) in userField.fieldArray('tags').fields.value" :key = "tagIndex" >
< input v-bind = "tagField.inputAttrs.value" />
< button @click = "userField.fieldArray('tags').remove(tagIndex)" > Remove Tag </ button >
</ div >
< button @click = "userField.fieldArray('tags').add('')" > Add Tag </ button >
< button @click = "usersArray.remove(userIndex)" > Remove User </ button >
</ div >
</ template >
useField
Hook to access form fields from an injected form instance. This is useful for creating reusable form components that don’t need direct access to the form instance.
function useField < T = any >( path : string , options ?: FieldOptions ) : FormField < T >
Parameters
The field path (e.g., “name”, “user.email”, “items[0].title”)
Field options (type, value for checkboxes)
Returns
A FormField instance for the specified path
Usage
<!-- ParentComponent.vue -->
< script setup >
import { useLiveForm } from 'live_vue'
import TextInput from './TextInput.vue'
const form = useLiveForm (() => props . form , {
changeEvent: 'validate'
})
// Form instance is automatically provided to child components
</ script >
< template >
< form >
< TextInput path = "name" label = "Name" />
< TextInput path = "email" label = "Email" />
</ form >
</ template >
<!-- TextInput.vue -->
< script setup >
import { useField } from 'live_vue'
interface Props {
path : string
label : string
}
const props = defineProps < Props >()
const field = useField ( props . path )
</ script >
< template >
< div class = "field" >
< label :for = "field.inputAttrs.value.id" > {{ label }} </ label >
< input v-bind = "field.inputAttrs.value" />
< span v-if = "field.errorMessage.value" class = "error" >
{{ field.errorMessage.value }}
</ span >
</ div >
</ template >
Error handling
Throws an error if used outside a component where a form has been provided:
useField() can only be used inside components where a form has been provided.
Make sure to use useLiveForm() in a parent component.
useArrayField
Hook to access form array fields from an injected form instance.
function useArrayField < T = any >( path : string ) : FormFieldArray < T >
Parameters
The field path for an array field (e.g., “items”, “user.tags”, “posts[0].comments”)
Returns
A FormFieldArray instance for the specified path
Usage
<!-- ParentComponent.vue -->
< script setup >
import { useLiveForm } from 'live_vue'
import TagList from './TagList.vue'
const form = useLiveForm (() => props . form )
</ script >
< template >
< form >
< TagList path = "tags" />
</ form >
</ template >
<!-- TagList.vue -->
< script setup >
import { useArrayField } from 'live_vue'
interface Props {
path : string
}
const props = defineProps < Props >()
const tagsArray = useArrayField < string >( props . path )
</ script >
< template >
< div >
< div v-for = "(tagField, index) in tagsArray.fields.value" :key = "index" >
< input v-bind = "tagField.inputAttrs.value" />
< button @click = "tagsArray.remove(index)" > Remove </ button >
</ div >
< button @click = "tagsArray.add('')" > Add Tag </ button >
</ div >
</ template >
Error handling
Throws an error if used outside a component where a form has been provided:
useArrayField() can only be used inside components where a form has been provided.
Make sure to use useLiveForm() in a parent component.