Lzh on GitHub

Form

一个内置验证和提交处理的表单组件。

用法

使用 Form 组件可以通过 Valibot, Zod, Yup, Joi, Superstruct 等验证库或你自己的验证逻辑来验证表单数据。

它与 FormField 组件配合使用,可自动显示表单元素周围的错误消息。

模式验证 (Schema Validation)

它需要两个 props:

默认 不包含验证库,请确保你 安装了所需的验证库
<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>

错误会根据 nameerror-pattern prop 直接报告给 FormField 组件。这意味着你的模式中为 email 属性定义的验证规则将应用于 <FormField name="email">

嵌套验证规则使用点符号处理。例如,像 { user: z.object({ email: z.string() }) } 这样的规则将应用于 <FormField name="user.email">

自定义验证 (Custom Validation)

使用 validate prop 来应用你自己的验证逻辑。

验证函数必须返回一个错误列表,其中包含以下属性:

  • message - 要显示的错误消息。
  • name - 要发送错误的 FormFieldname
它可以与 schema prop 一起使用,以处理复杂的用例。
<script setup lang="ts">
import type { FormError, FormSubmitEvent } from '@nuxt/ui'

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

const validate = (state: any): 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<typeof state>) {
  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>

输入事件 (Input Events)

当输入发出 inputchangeblur 事件时,Form 组件会自动触发验证。

  • input 上的验证发生在你输入时。
  • change 上的验证发生在你提交值时。
  • blur 上的验证发生在输入失去焦点时。

你可以使用 validate-on prop 控制何时进行验证。

在表单提交时,验证将自动触发。
你可以使用 useFormField 可组合项在自己的组件中实现这一点。

错误事件 (Error Event)

你可以监听 @error 事件来处理错误。此事件在表单提交时触发,并包含一个 FormError 对象数组,其中包含以下字段:

  • id - 输入的 id
  • name - FormFieldname
  • message - 要显示的错误消息。

以下是一个示例,它在表单提交后将焦点设置在第一个有错误的输入元素上:

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

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

const validate = (state: any): 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<typeof state>) {
  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>

嵌套表单 (Nesting Forms)

嵌套表单组件允许你更有效地管理复杂的数据结构,例如列表或条件字段。

例如,它可以用于根据用户输入动态添加字段:

<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.string().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
    :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" :state="state" :schema="nestedSchema" attach>
      <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 * 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>[] }>>({
  items: [{}]
})

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"
      :state="item"
      :schema="itemSchema"
      attach
      class="flex gap-2"
    >
      <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
state

any

An object representing the current state of the form.

id

string | number

schema

any

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

validate

(state: any): 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.

validateOnInputDelay

300

number

Delay in milliseconds before validating the form on input events.

transform

true

boolean

If true, schema transformations will be applied to the state on submit.

attach

true

boolean

If true, this form will attach to its parent Form (if any) 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.

Slots

Slot Type
default

{ errors: FormError<string>[]; loading: boolean; }

Emits

Event Type
error

FormErrorEvent

submit

FormSubmitEvent<any>

Expose

你可以使用 useTemplateRef 访问类型化的组件实例。

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

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

这将允许你访问以下内容:

NameType
submit()Promise<void>

Triggers form submission.

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

Triggers form validation. Will raise any errors unless opts.silent is set to true.

clear(path?: keyof T)void

Clears form errors associated with a specific path. If no path is provided, clears all form errors.

getErrors(path?: keyof T)FormError[]

Retrieves form errors associated with a specific path. If no path is provided, returns all form errors.

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

Sets form errors for a given path. If no path is provided, overrides all errors.

errorsRef<FormError[]>

A reference to the array containing validation errors. Use this to access or manipulate the error information.

disabledRef<boolean>
dirtyRef<boolean> true if at least one form field has been updated by the user.
dirtyFieldsDeepReadonly<Set<keyof T>> Tracks fields that have been modified by the user.
touchedFieldsDeepReadonly<Set<keyof T>> Tracks fields that the user interacted with.
blurredFieldsDeepReadonly<Set<keyof T>> Tracks fields blurred by the user.

在 UForm 之外,使用 UButton 提交表单

在 Nuxt UI Pro 中(基于 UForm),你可以在 UForm 组件外部使用按钮提交表单,只需要通过 ref 引用表单实例,然后调用其 submit() 方法即可。

  1. <UForm> 添加 ref
<UForm ref="formRef" :state="form" @submit="handleSubmit">
  <!-- 表单字段 -->
</UForm>
  1. <script setup> 中定义
const formRef = ref()
const form = reactive({
  name: '',
  email: ''
})

function handleSubmit(state: typeof form) {
  console.log('提交成功:', state)
}
  1. 表单外部按钮调用 .submit()
<UButton @click="formRef?.submit()">提交表单</UButton>

Theme

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: ''
        }
      }
    })
  ]
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import uiPro from '@nuxt/ui-pro/vite'

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