Skip to main content
You are viewing Pre-Alpha documentation.
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One

Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

Form

A headless form component that coordinates validation across child fields. Renders a native <form> element and intercepts submit/reset — child inputs using createValidation (including Input.Root) auto-register via useForm() injection.


IntermediateMar 17, 2026

Usage

Wrap your inputs in <Form>. Native <button type="submit"> and <button type="reset"> work as expected.

<script setup lang="ts">
  import { Form, Input } from '@vuetify/v0'
  import { shallowRef } from 'vue'

  const valid = shallowRef<boolean | null>(null)
  const email = shallowRef('')
  const password = shallowRef('')
  const submitted = shallowRef(false)

  function onSubmit ({ valid }: { valid: boolean }) {
    if (!valid) return

    submitted.value = true
  }

  function onReset () {
    email.value = ''
    password.value = ''
    submitted.value = false
  }
</script>

<template>
  <Form v-model="valid" class="flex flex-col gap-4 max-w-sm mx-auto" @reset="onReset" @submit="onSubmit">
    <Input.Root
      id="email"
      v-model="email"
      label="Email"
      :rules="[
        (v: string) => !!v || 'Email is required',
        (v: string) => /.+@.+\..+/.test(v) || 'Must be a valid email',
      ]"
      type="email"
    >
      <label class="text-sm font-medium text-on-surface" for="email">Email</label>

      <Input.Control
        class="w-full px-3 py-2 rounded-lg border border-divider bg-surface text-on-surface placeholder:text-on-surface-variant/50 outline-none data-[focused]:border-primary data-[state=invalid]:border-error transition-colors"
        placeholder="you@example.com"
      />

      <Input.Error v-slot="{ errors }" class="text-xs text-error">
        <span v-for="error in errors" :key="error">{{ error }}</span>
      </Input.Error>
    </Input.Root>

    <Input.Root
      id="password"
      v-model="password"
      label="Password"
      :rules="[
        (v: string) => !!v || 'Password is required',
        (v: string) => v.length >= 8 || 'Must be at least 8 characters',
      ]"
      type="password"
    >
      <label class="text-sm font-medium text-on-surface" for="password">Password</label>

      <Input.Control
        class="w-full px-3 py-2 rounded-lg border border-divider bg-surface text-on-surface placeholder:text-on-surface-variant/50 outline-none data-[focused]:border-primary data-[state=invalid]:border-error transition-colors"
        placeholder="••••••••"
      />

      <Input.Error v-slot="{ errors }" class="text-xs text-error">
        <span v-for="error in errors" :key="error">{{ error }}</span>
      </Input.Error>
    </Input.Root>

    <div class="flex gap-2">
      <button
        class="px-4 py-2 rounded-lg bg-primary text-on-primary font-medium text-sm"
        type="submit"
      >
        Sign in
      </button>

      <button
        class="px-4 py-2 rounded-lg border border-divider text-on-surface text-sm"
        type="reset"
      >
        Reset
      </button>
    </div>

    <p v-if="submitted" class="text-sm text-success">
      Form submitted successfully!
    </p>
  </Form>
</template>

Anatomy

vue
<script setup lang="ts">
  import { Form, Input } from '@vuetify/v0'
</script>

<template>
  <Form>
    <Input.Root>
      <Input.Control />
      <Input.Error />
    </Input.Root>
  </Form>
</template>

Architecture

Form creates a createForm instance and provides it via useForm(). Child validations auto-register on mount and unregister on unmount.

Form Architecture

Use controls to zoom and pan. Click outside or press Escape to close.

Form Architecture

The @submit event is pass-through — it always fires, regardless of validity. Guard in your handler:

ts
function onSubmit ({ valid }: { valid: boolean }) {
  if (!valid) return
  // handle submission
}

Accessibility

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.

Keyboard Interaction

KeyBehavior
Enter (in input)Submits the form
EscapeNo default behavior — handle in your submit handler

API Reference

The following API details are for all variations of the Form component.

Form

Props

namespace

string

Namespace for dependency injection

Default: "v0:form"

disabled

boolean

Disables all registered fields

Default: false

readonly

boolean

Sets all registered fields to readonly

Default: false

modelValue

boolean

Default: null

Events

update:model-value

[value: boolean]

submit

[payload: { valid: boolean; }]

Slots

default

FormSlotProps

Recipes

Slot Props

Use slot props for reactive form state in the template:

vue
<template>
  <Form v-slot="{ isValid, isValidating, submit, reset }">
    <!-- Inputs -->

    <button type="submit" :disabled="isValidating">
      {{ isValidating ? 'Validating…' : 'Submit' }}
    </button>

    <p v-if="isValid === false">Please fix the errors above.</p>
  </Form>
</template>

Disabled / Readonly

The disabled and readonly props propagate to the form context. Child components can read them via useForm():

vue
<template>
  <Form disabled>
    <!-- All fields read form.disabled via useForm() -->
  </Form>
</template>

Custom Namespace

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>

Programmatic Submit

Call submit() from slot props when you need to trigger validation without a submit button:

vue
<template>
  <Form v-slot="{ submit }">
    <!-- Inputs -->

    <button type="button" @click="submit">Save Draft</button>
  </Form>
</template>
Tip

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.

Was this page helpful?

© 2016-1970 Vuetify, LLC
Ctrl+/