A complete account registration form that shows how Form coordinates validation across several Input.Root fields and reports a single aggregate result. The form gathers name, email, password, and a confirmation, validates each field on blur, and only commits when every child validation passes.
The interesting part is cross-field validation. The confirmation field’s rule compares its value against the live password model rather than a static value, so changing the password re-evaluates whether the two still match. Because Form’s @submit event is pass-through — it fires on every native submit regardless of validity — the handler guards on payload.valid before doing any work, then simulates a server-side duplicate-account check that surfaces through the email field’s error / error-messages props. The composable owns the field state and the submitted/server-error flags, so the entry component can swap the form for a success panel without the form needing to know about it.
Reach for this pattern whenever a form is more than a single field: extract the state and submit logic into a use* composable, keep the markup in a reusable component, and let the entry wire them together. For the underlying validation primitives, see createForm and createValidation; for individual field anatomy, see Input.
File
Role
useSignup.ts
Composable — field state, submit/reset logic, simulated server error
SignupForm.vue
Reusable component — renders the Form with four validated Input.Root fields and a cross-field match rule
signup-form.vue
Entry — wires the composable to the form and swaps in a success panel on submit
Try submitting with taken@example.com to see server-side error injection.
Use namespace to isolate multiple forms on the same page:
vue
<template> <Form namespace="billing"> <!-- useForm('billing') resolves this form --> </Form> <Form namespace="shipping"> <!-- useForm('shipping') resolves this form --> </Form></template>
Calling submit() or reset() via slot props invokes the form methods directly and does not emit @submit or @reset. Those events only fire from native form submission/reset.
Form renders a native <form> element, so all standard form semantics apply. No custom ARIA is needed — the browser handles submit on Enter, associates labels with inputs via id/for, and reports validation errors to assistive technology through child inputs.
@submit is pass-through — it fires on every native submit regardless of validity. Guard inside the handler: read the valid flag from the payload and return early when it’s false.
Give each a namespace (e.g. namespace="billing"). Children then resolve their form with useForm('billing'), so the two forms stay isolated.
submit() and reset() from slot props invoke the form methods directly. The @submit and @reset events only fire from native form submission or reset.
Discord
Need help? Join our community for support and discussions ↗