Form

GitHub
Een formuliercomponent met ingebouwde validatie en afhandeling van inzendingen.

Gebruik

Gebruik de Form-component om formuliergegevens te valideren met behulp van een validatiebibliotheek die Standard ondersteunt Schema zoals Valibot, Zod, Regle, Yup, Joi of Superstruct of uw eigen validatielogica.

Het werkt met de FormField component om foutmeldingen rond formulierelementen automatisch weer te geven.

Schema validatie

Het vereist twee rekwisieten:

Standaard is er geen validatiebibliotheek opgenomen, zorg ervoor dat u installeert degene die u nodig hebt.
<script setup lang="ts">
import * as v from 'valibot'
import type { FormSubmitEvent } from '@nuxt/ui'

const schema = v.object({
  email: v.pipe(v.string(), v.email('Invalid email')),
  password: v.pipe(v.string(), v.minLength(8, 'Must be at least 8 characters'))
})

type Schema = v.InferOutput<typeof schema>

const state = reactive({
  email: '',
  password: ''
})

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
    <UFormField label="Email" name="email">
      <UInput v-model="state.email" />
    </UFormField>

    <UFormField label="Password" name="password">
      <UInput v-model="state.password" type="password" />
    </UFormField>

    <UButton type="submit">
      Submit
    </UButton>
  </UForm>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import * as v from 'valibot'
import type { FormSubmitEvent } from '@nuxt/ui'

const schema = v.object({
  email: v.pipe(v.string(), v.email('Invalid email')),
  password: v.pipe(v.string(), v.minLength(8, 'Must be at least 8 characters'))
})

type Schema = v.InferOutput<typeof schema>

const state = reactive({
  email: '',
  password: ''
})

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
    <UFormField label="Email" name="email">
      <UInput v-model="state.email" />
    </UFormField>

    <UFormField label="Password" name="password">
      <UInput v-model="state.password" type="password" />
    </UFormField>

    <UButton type="submit">
      Submit
    </UButton>
  </UForm>
</template>

Aangepaste validatie

Gebruik de validate prop om je eigen validatielogica toe te passen.

De validatiefunctie moet een lijst met fouten retourneren met de volgende kenmerken:

  • message - het te tonen foutbericht.
  • name - de name van de FormField om de fout naar toe te sturen.
Het kan naast de schema-prop worden gebruikt om complexe use-cases te behandelen.
<script setup lang="ts">
import type { FormError, FormSubmitEvent } from '@nuxt/ui'

const state = reactive({
  email: undefined,
  password: undefined
})

type Schema = typeof state

function validate(state: Partial<Schema>): FormError[] {
  const errors = []
  if (!state.email) errors.push({ name: 'email', message: 'Required' })
  if (!state.password) errors.push({ name: 'password', message: 'Required' })
  return errors
}

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit">
    <UFormField label="Email" name="email">
      <UInput v-model="state.email" />
    </UFormField>

    <UFormField label="Password" name="password">
      <UInput v-model="state.password" type="password" />
    </UFormField>

    <UButton type="submit">
      Submit
    </UButton>
  </UForm>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import type { FormError, FormSubmitEvent } from '@nuxt/ui'

const state = reactive({
  email: undefined,
  password: undefined
})

type Schema = typeof state

function validate(state: Partial<Schema>): FormError[] {
  const errors = []
  if (!state.email) errors.push({ name: 'email', message: 'Required' })
  if (!state.password) errors.push({ name: 'password', message: 'Required' })
  return errors
}

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit">
    <UFormField label="Email" name="email">
      <UInput v-model="state.email" />
    </UFormField>

    <UFormField label="Password" name="password">
      <UInput v-model="state.password" type="password" />
    </UFormField>

    <UButton type="submit">
      Submit
    </UButton>
  </UForm>
</template>

Foutmelding

Fouten worden gekoppeld aan de overeenkomstige FormField met behulp van de name-prop. Een fout in het email-veld wordt weergegeven door <FormField name="email">.

Genestelde velden worden gematcht met behulp van puntnotatie. Een schema zoals { user: z.object({ email: z.string() }) } zal worden toegepast op <FormField name="user.email">.

Fouten op array-items bevatten de index in hun naam (bijv. tags.0, tags.1) en komen niet overeen met <FormField name="tags"> alleen door name. Gebruik de error-pattern prop met een reguliere expressie zoals /^tags\..+/ om ze vast te leggen. Dit is vooral handig voor componenten zoals InputTags.
<script setup lang="ts">
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'

const schema = z.object({
  email: z.email('Invalid email'),
  tags: z.array(z.string().regex(/^[a-z-]+$/, 'Lowercase letters and dashes only')).min(1, 'Please add at least one tag')
})

type Schema = z.output<typeof schema>

const state = reactive<Partial<Schema>>({
  email: undefined,
  tags: []
})

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
    <UFormField label="Email" name="email">
      <UInput v-model="state.email" />
    </UFormField>

    <UFormField label="Tags" name="tags" :error-pattern="/^tags\..+/">
      <UInputTags v-model="state.tags" />
    </UFormField>

    <UButton type="submit">
      Submit
    </UButton>
  </UForm>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'

const schema = z.object({
  email: z.email('Invalid email'),
  tags: z.array(z.string().regex(/^[a-z-]+$/, 'Lowercase letters and dashes only')).min(1, 'Please add at least one tag')
})

type Schema = z.output<typeof schema>

const state = reactive<Partial<Schema>>({
  email: undefined,
  tags: []
})

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
    <UFormField label="Email" name="email">
      <UInput v-model="state.email" />
    </UFormField>

    <UFormField label="Tags" name="tags" :error-pattern="/^tags\..+/">
      <UInputTags v-model="state.tags" />
    </UFormField>

    <UButton type="submit">
      Submit
    </UButton>
  </UForm>
</template>

Input gebeurtenissen

Het Form-onderdeel activeert automatisch validatie wanneer een invoer een input, change of blur-gebeurtenis uitzendt.

  • Validatie op input komt voor as u type.
  • Validatie op change treedt op wanneer u commit naar een value.
  • Validatie op blur gebeurt wanneer een ingang loses focus.

U kunt dit controleren wanneer validatie plaatsvindt met behulp van de validate-on prop.

Het formulier valideert altijd bij verzending.
mm
dd
yyyy
––
––
AM
Option 1
Option 2
Option 3
Option 1
Option 2
Option 3
Drop your image here
PNG (max. 1MB)
CheckboxGroup
RadioGroup
U kunt de useFormField composable gebruiken om dit in uw eigen componenten te implementeren.

Fout gebeurtenis

U kunt naar de @error-gebeurtenis luisteren om fouten te verwerken. Deze gebeurtenis wordt geactiveerd wanneer het formulier wordt ingediend en bevat een reeks FormError-objecten met de volgende velden:

  • id - de invoer is id.
  • name - de name van de FormField
  • message - het te tonen foutbericht.

Hier is een voorbeeld dat het eerste invoerelement focust met een fout nadat het formulier is ingediend:

<script setup lang="ts">
import type { FormError, FormErrorEvent, FormSubmitEvent } from '@nuxt/ui'

const state = reactive({
  email: undefined,
  password: undefined
})

type Schema = typeof state

function validate(state: Partial<Schema>): FormError[] {
  const errors = []
  if (!state.email) errors.push({ name: 'email', message: 'Required' })
  if (!state.password) errors.push({ name: 'password', message: 'Required' })
  return errors
}

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}

async function onError(event: FormErrorEvent) {
  if (event?.errors?.[0]?.id) {
    const element = document.getElementById(event.errors[0].id)
    element?.focus()
    element?.scrollIntoView({ behavior: 'smooth', block: 'center' })
  }
}
</script>

<template>
  <UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit" @error="onError">
    <UFormField label="Email" name="email">
      <UInput v-model="state.email" />
    </UFormField>

    <UFormField label="Password" name="password">
      <UInput v-model="state.password" type="password" />
    </UFormField>

    <UButton type="submit">
      Submit
    </UButton>
  </UForm>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import type { FormError, FormErrorEvent, FormSubmitEvent } from '@nuxt/ui'

const state = reactive({
  email: undefined,
  password: undefined
})

type Schema = typeof state

function validate(state: Partial<Schema>): FormError[] {
  const errors = []
  if (!state.email) errors.push({ name: 'email', message: 'Required' })
  if (!state.password) errors.push({ name: 'password', message: 'Required' })
  return errors
}

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}

async function onError(event: FormErrorEvent) {
  if (event?.errors?.[0]?.id) {
    const element = document.getElementById(event.errors[0].id)
    element?.focus()
    element?.scrollIntoView({ behavior: 'smooth', block: 'center' })
  }
}
</script>

<template>
  <UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit" @error="onError">
    <UFormField label="Email" name="email">
      <UInput v-model="state.email" />
    </UFormField>

    <UFormField label="Password" name="password">
      <UInput v-model="state.password" type="password" />
    </UFormField>

    <UButton type="submit">
      Submit
    </UButton>
  </UForm>
</template>

HTML5 validatie: badge{label="4.5+" class="align-text-top"}

Wanneer form.submit() programmatisch wordt aangeroepen, activeert de Form-component automatisch native HTML5-validatie voordat deze wordt ingediend.

Dit is vooral handig wanneer de verzendknop zich buiten het formulierelement bevindt, zoals in een modale voettekst.
<script setup lang="ts">
import type { FormSubmitEvent } from '@nuxt/ui'

const state = reactive({
  email: undefined,
  age: undefined
})

type Schema = typeof state

const form = useTemplateRef('form')

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <div class="space-y-4">
    <UForm ref="form" :state="state" class="space-y-4" @submit="onSubmit">
      <UFormField label="Email" name="email">
        <UInput v-model="state.email" type="email" required />
      </UFormField>

      <UFormField label="Age" name="age">
        <UInput v-model="state.age" type="number" min="18" max="100" required />
      </UFormField>
    </UForm>

    <UButton @click="form?.submit()">
      Submit
    </UButton>
  </div>
</template>
<script setup lang="ts">
import { reactive, useTemplateRef } from 'vue'
import type { FormSubmitEvent } from '@nuxt/ui'

const state = reactive({
  email: undefined,
  age: undefined
})

type Schema = typeof state

const form = useTemplateRef('form')

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <div class="space-y-4">
    <UForm ref="form" :state="state" class="space-y-4" @submit="onSubmit">
      <UFormField label="Email" name="email">
        <UInput v-model="state.email" type="email" required />
      </UFormField>

      <UFormField label="Age" name="age">
        <UInput v-model="state.age" type="number" min="18" max="100" required />
      </UFormField>
    </UForm>

    <UButton @click="form?.submit()">
      Submit
    </UButton>
  </div>
</template>

Nestvormen

Gebruik de nested prop om meerdere Form-componenten te nesten en hun validatiefuncties te koppelen. In dit geval zal het valideren van het bovenliggende formulier automatisch alle andere formulieren erin valideren.

Genestelde formulieren erven rechtstreeks de staat van hun ouders, dus u hoeft geen afzonderlijke staat voor hen te definiëren. U kunt de name-prop gebruiken om een genest attribuut binnen de status van de ouder te targeten.

Het kan worden gebruikt om dynamisch velden toe te voegen op basis van input van de gebruiker:

<script setup lang="ts">
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'

const schema = z.object({
  name: z.string().min(2),
  news: z.boolean().default(false)
})

type Schema = z.output<typeof schema>

const nestedSchema = z.object({
  email: z.email()
})

type NestedSchema = z.output<typeof nestedSchema>

const state = reactive<Partial<Schema & NestedSchema>>({ })

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <UForm
    ref="form"
    :state="state"
    :schema="schema"
    class="gap-4 flex flex-col w-60"
    @submit="onSubmit"
  >
    <UFormField label="Name" name="name">
      <UInput v-model="state.name" placeholder="John Lennon" />
    </UFormField>

    <div>
      <UCheckbox v-model="state.news" name="news" label="Register to our newsletter" @update:model-value="state.email = undefined" />
    </div>

    <UForm v-if="state.news" :schema="nestedSchema" nested>
      <UFormField label="Email" name="email">
        <UInput v-model="state.email" placeholder="john@lennon.com" />
      </UFormField>
    </UForm>

    <div>
      <UButton type="submit">
        Submit
      </UButton>
    </div>
  </UForm>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'

const schema = z.object({
  name: z.string().min(2),
  news: z.boolean().default(false)
})

type Schema = z.output<typeof schema>

const nestedSchema = z.object({
  email: z.email()
})

type NestedSchema = z.output<typeof nestedSchema>

const state = reactive<Partial<Schema & NestedSchema>>({ })

const toast = useToast()
async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <UForm
    ref="form"
    :state="state"
    :schema="schema"
    class="gap-4 flex flex-col w-60"
    @submit="onSubmit"
  >
    <UFormField label="Name" name="name">
      <UInput v-model="state.name" placeholder="John Lennon" />
    </UFormField>

    <div>
      <UCheckbox v-model="state.news" name="news" label="Register to our newsletter" @update:model-value="state.email = undefined" />
    </div>

    <UForm v-if="state.news" :schema="nestedSchema" nested>
      <UFormField label="Email" name="email">
        <UInput v-model="state.email" placeholder="john@lennon.com" />
      </UFormField>
    </UForm>

    <div>
      <UButton type="submit">
        Submit
      </UButton>
    </div>
  </UForm>
</template>

Of om lijstinvoer te valideren:

<script setup lang="ts">
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'

const schema = z.object({
  customer: z.string().min(2)
})

type Schema = z.output<typeof schema>

const itemSchema = z.object({
  description: z.string().min(1),
  price: z.number().min(0)
})

type ItemSchema = z.output<typeof itemSchema>

const state = reactive<Partial<Schema & { items: Partial<ItemSchema>[] }>>({ })

function addItem() {
  if (!state.items) {
    state.items = []
  }
  state.items.push({})
}

function removeItem() {
  if (state.items) {
    state.items.pop()
  }
}

const toast = useToast()

async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <UForm
    :state="state"
    :schema="schema"
    class="gap-4 flex flex-col w-60"
    @submit="onSubmit"
  >
    <UFormField label="Customer" name="customer">
      <UInput v-model="state.customer" placeholder="Wonka Industries" />
    </UFormField>

    <UForm
      v-for="item, count in state.items"
      :key="count"
      :name="`items.${count}`"
      :schema="itemSchema"
      class="flex gap-2"
      nested
    >
      <UFormField :label="!count ? 'Description' : undefined" name="description">
        <UInput v-model="item.description" />
      </UFormField>
      <UFormField :label="!count ? 'Price' : undefined" name="price" class="w-20">
        <UInput v-model="item.price" type="number" />
      </UFormField>
    </UForm>

    <div class="flex gap-2">
      <UButton color="neutral" variant="subtle" size="sm" @click="addItem()">
        Add Item
      </UButton>

      <UButton color="neutral" variant="ghost" size="sm" @click="removeItem()">
        Remove Item
      </UButton>
    </div>
    <div>
      <UButton type="submit">
        Submit
      </UButton>
    </div>
  </UForm>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import * as z from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'

const schema = z.object({
  customer: z.string().min(2)
})

type Schema = z.output<typeof schema>

const itemSchema = z.object({
  description: z.string().min(1),
  price: z.number().min(0)
})

type ItemSchema = z.output<typeof itemSchema>

const state = reactive<Partial<Schema & { items: Partial<ItemSchema>[] }>>({ })

function addItem() {
  if (!state.items) {
    state.items = []
  }
  state.items.push({})
}

function removeItem() {
  if (state.items) {
    state.items.pop()
  }
}

const toast = useToast()

async function onSubmit(event: FormSubmitEvent<Schema>) {
  toast.add({ title: 'Success', description: 'The form has been submitted.', color: 'success' })
  console.log(event.data)
}
</script>

<template>
  <UForm
    :state="state"
    :schema="schema"
    class="gap-4 flex flex-col w-60"
    @submit="onSubmit"
  >
    <UFormField label="Customer" name="customer">
      <UInput v-model="state.customer" placeholder="Wonka Industries" />
    </UFormField>

    <UForm
      v-for="item, count in state.items"
      :key="count"
      :name="`items.${count}`"
      :schema="itemSchema"
      class="flex gap-2"
      nested
    >
      <UFormField :label="!count ? 'Description' : undefined" name="description">
        <UInput v-model="item.description" />
      </UFormField>
      <UFormField :label="!count ? 'Price' : undefined" name="price" class="w-20">
        <UInput v-model="item.price" type="number" />
      </UFormField>
    </UForm>

    <div class="flex gap-2">
      <UButton color="neutral" variant="subtle" size="sm" @click="addItem()">
        Add Item
      </UButton>

      <UButton color="neutral" variant="ghost" size="sm" @click="removeItem()">
        Remove Item
      </UButton>
    </div>
    <div>
      <UButton type="submit">
        Submit
      </UButton>
    </div>
  </UForm>
</template>

API

Props

Prop Default Type
id
string | number
schema
S

Schema to validate the form state. Supports Standard Schema objects, Yup, Joi, and Superstructs.

state
N extends false ? Partial<InferInput<S>> : never

An object representing the current state of the form.

validate
(state: Partial<InferInput<S>>): FormError<string>[] | Promise<FormError<string>[]>

Custom validation function to validate the form state.

validateOn
`['blur', 'change', 'input']`
FormInputEvents[]

The list of input events that trigger the form validation.

disabled
boolean

Disable all inputs inside the form.

name
string

The name attribute of the form element. For nested forms (nested is true), this is also used as the path of the form's state within its parent form.

validateOnInputDelay
300
number

Delay in milliseconds before validating the form on input events.

transform
true as T
T

If true, applies schema transformations on submit.

nested
`false`
N

If true, this form will attach to its parent Form and validate at the same time.

loadingAuto
true
boolean

When true, all form elements will be disabled on @submit event. This will cause any focused input elements to lose their focus state.

acceptcharset
string
action
string
autocomplete
string
enctype
string
method
string
novalidate
false | true | "true" | "false"
target
string
ui
{ base?: any; }
Dit onderdeel ondersteunt ook alle native <form> HTML-kenmerken.

Slots

Slot Type
default
{ errors: FormErrorWithId[]; loading: boolean; }

Uitzendt

Event Type
submit
[event: FormSubmitEvent<FormData<S, T>>]
error
[event: FormErrorEvent]

Expose

U hebt toegang tot de getypte componentinstantie met useTemplateRef.

<script setup lang="ts">
const form = useTemplateRef('form')
</script>

<template>
  <UForm ref="form" />
</template>

Dit geeft u toegang tot het volgende:

NaamType
submit()Promise<void>

Triggers formulier indienen met HTML5-validatie.

validate(opts: { name?: keyof T | (keyof T)[], silent?: boolean, nested?: boolean, transform?: boolean })Promise<T>

Triggers formuliervalidatie. Zal fouten veroorzaken tenzij opts.silent is ingesteld op true.

clear(path?: keyof T | RegExp)void

Clears formulierfouten die aan een specifiek pad zijn gekoppeld. Als er geen pad is opgegeven, worden alle formulierfouten verwijderd.

getErrors(path?: keyof T | RegExp)FormErrorWithId[]

Retrieves formulierfouten die aan een specifiek pad zijn gekoppeld. Als er geen pad is opgegeven, worden alle formulierfouten geretourneerd.

setErrors(errors: FormError[], name?: keyof T | RegExp)void

Stelt formulierfouten in voor een bepaald pad. Als er geen pad is opgegeven, worden alle fouten overschreven.

errorsRef<FormErrorWithId[]>

Een verwijzing naar de array die validatiefouten bevat. Gebruik dit om de foutinformatie te openen of te manipuleren.

disabledRef<boolean>
dirtyRef<boolean> true als ten minste één formulierveld is bijgewerkt door de gebruiker.
dirtyFieldsReadonlySet<DeepReadonly<keyof T>> Tracks velden die zijn gewijzigd door de gebruiker.
touchedFieldsReadonlySet<DeepReadonly<keyof T>> Houdt velden bij waarmee de gebruiker interactie heeft gehad.
blurredFieldsReadonlySet<DeepReadonly<keyof T>> Velden bijhouden die door de gebruiker zijn vervaagd.

Thema

app.config.ts
export default defineAppConfig({
  ui: {
    form: {
      base: ''
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        form: {
          base: ''
        }
      }
    })
  ]
})

Changelog

No recent changes