LiveVue 1.0
Examples Simple Form

Simple Form

Build forms with useLiveForm() that sync with Ecto changesets. Server-side validation with real-time feedback, all powered by Phoenix LiveView.

What this example shows

1
useLiveForm()
Form state management
2
Ecto Changeset
Server-side validation
3
Debounced Events
Efficient validation
UnchangedInvalid

How it works

1 Initialize the form with useLiveForm()

The useLiveForm() composable takes the form prop and configuration. It returns reactive form state and field accessors.

const form = useLiveForm(() => props.form, {
  changeEvent: "validate",
  submitEvent: "submit",
  debounceInMiliseconds: 300
})

2 Access fields with form.field()

Each field provides reactive state including value, errors (array), errorMessage (first error shortcut), isTouched, and inputAttrs for binding.

const nameField = form.field("name")
// errorMessage.value is equivalent to errors.value[0]
// Use: nameField.inputAttrs.value, nameField.errorMessage.value

3 Error display strategies

You can display validation errors in different ways depending on UX needs. This example shows errors only after the user has interacted with a field (isTouched), which prevents showing errors before the user has had a chance to fill in the form.

<!-- Show error only after user interaction -->
<div v-if="nameField.isTouched.value && nameField.errorMessage.value">
  {{ nameField.errorMessage.value }}
</div>

<!-- Always show error (useful after submit) -->
<div v-if="nameField.errorMessage.value">...</div>

<!-- Show all errors for a field -->
<div v-for="error in nameField.errors.value">{{ error }}</div>

4 Server validates with Ecto changeset

The LiveView uses Ecto.Changeset for validation. Use to_form() to convert the changeset to a form that LiveVue understands.

changeset = changeset(params) |> Map.put(:action, :validate)
assign(socket, form: to_form(changeset, as: :contact))

5 Bind inputs with v-bind and inputAttrs

Use v-bind with inputAttrs.value to automatically wire up value, name, id, and event handlers.

<input v-bind="nameField.inputAttrs.value" type="text" />

6 Submit and reset the form

Call form.submit() to send the submitEvent to the server, and form.reset() to restore initial values and clear touched state.

<button @click="form.submit()" :disabled="!form.isValid.value">
  Submit
</button>
<button @click="form.reset()">Reset</button>

7 Reset form state after successful submit

When resetting the form after a successful submission, return {:reply, %{reset: true}, socket} instead of {:noreply, socket}. This tells useLiveForm() to clear the client-side touched state, preventing validation errors from showing on the fresh form.

def handle_event("submit", params, socket) do
  %{"contact" => form_params} = params
  changeset = changeset(form_params)
              |> Map.put(:action, :insert)

  if changeset.valid? do
    # Reply with reset: true to clear touched state
    new_form = to_form(
      changeset(%{}),
      as: :contact
    )
    {:reply, %{reset: true},
     assign(socket, form: new_form)}
  else
    {:noreply,
     assign(socket,
      form: to_form(changeset, as: :contact))}
  end
end
Next up: Nested Objects with dot notation paths
View example