Стандарты

Формы и валидация

Стандарт валидации форм с использованием Nuxt UI 4 и Zod.

Стандарт валидации форм с использованием Nuxt UI 4 и Zod.

Структура формы (Nuxt UI 4)

В Nuxt UI 4 для форм используются:

  • UForm — контейнер формы с валидацией (Zod schema)
  • UFormField — обёртка для полей с label и error (заменяет UFormGroup из v3)
  • UInput, UTextarea, USelect — инпуты
<template>
  <UForm :schema="schema" :state="state" @submit="onSubmit">
    <UFormField label="Email" name="email">
      <UInput v-model="state.email" placeholder="email@example.com" />
    </UFormField>

    <UFormField label="Пароль" name="password">
      <UInput v-model="state.password" type="password" />
    </UFormField>

    <UFormField label="Телефон" name="phone">
      <UInput v-model="state.phone" placeholder="+7 (___) ___-__-__" />
    </UFormField>

    <UButton type="submit" :loading="loading">
      Сохранить
    </UButton>
  </UForm>
</template>

<script setup lang="ts">
import { z } from 'zod'
import type { FormSubmitEvent } from '#ui/types'

const loading = ref(false)

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

const schema = z.object({
  email: z.string().email('Некорректный email'),
  password: z.string().min(8, 'Минимум 8 символов'),
  phone: z.string().regex(/^\+7 \(\d{3}\) \d{3}-\d{2}-\d{2}$/, 'Некорректный формат'),
})

type Schema = z.output<typeof schema>

async function onSubmit(event: FormSubmitEvent<Schema>) {
  loading.value = true
  try {
    await api.post('/users', event.data)
    // success
  } finally {
    loading.value = false
  }
}
</script>
Nuxt UI 4 vs UI 3: В Nuxt UI 4 компонент UFormGroup переименован в UFormField. Используйте UFormField для всех новых проектов.

Валидация с Zod

Базовые правила

import { z } from 'zod'

const schema = z.object({
  // Обязательное поле
  name: z.string().min(1, 'Введите имя'),

  // Email
  email: z.string().email('Некорректный email'),

  // Минимальная длина
  password: z.string().min(8, 'Минимум 8 символов'),

  // Паттерн
  bin: z.string().regex(/^\d{12}$/, 'БИН должен содержать 12 цифр'),

  // Опциональное
  phone: z.string().optional(),

  // Число
  age: z.number().min(18, 'Минимум 18 лет'),
})

Типовые схемы

// Телефон KZ
const phoneSchema = z.string().regex(
  /^\+7 \(\d{3}\) \d{3}-\d{2}-\d{2}$/,
  'Формат: +7 (XXX) XXX-XX-XX'
)

// БИН/ИИН
const binSchema = z.string().regex(/^\d{12}$/, '12 цифр')

// Email
const emailSchema = z.string().email('Некорректный email')

// Пароль
const passwordSchema = z.string()
  .min(8, 'Минимум 8 символов')
  .regex(/[A-Z]/, 'Минимум одна заглавная буква')
  .regex(/[0-9]/, 'Минимум одна цифра')

Компоненты формы

UFormField (Nuxt UI 4)

<UFormField
  label="Email"
  name="email"
  description="Используется для входа"
  hint="Необязательно"
  required
>
  <UInput v-model="state.email" />
</UFormField>
PropОписание
labelЛейбл поля
nameИмя поля (для валидации, должно соответствовать ключу в schema)
descriptionПодсказка под полем
hintТекст справа от лейбла
requiredПоказать звёздочку
errorРучная установка ошибки (для серверных ошибок)

UInput с маской

<script setup>
import { vMaska } from 'maska'
</script>

<template>
  <UInput
    v-model="state.phone"
    v-maska="'+7 (###) ###-##-##'"
    placeholder="+7 (___) ___-__-__"
  />
</template>

Типовые маски

ПолеМаска
Телефон KZ+7 (###) ###-##-##
БИН/ИИН############
Дата##.##.####
Банковский счётKZ## #### #### #### ####

Состояния формы

Loading

<UButton type="submit" :loading="loading">
  Сохранить
</UButton>

Disabled поля

<UInput v-model="state.email" :disabled="isReadonly" />

Ошибки с сервера

<script setup>
const serverErrors = ref<Record<string, string>>({})

async function onSubmit(event) {
  try {
    await api.post('/users', event.data)
  } catch (error) {
    if (error.status === 422) {
      serverErrors.value = error.details
    }
  }
}
</script>

<template>
  <UFormField label="Email" name="email" :error="serverErrors.email">
    <UInput v-model="state.email" />
  </UFormField>
</template>

Паттерн формы в модальном окне

<template>
  <UModal v-model="isOpen">
    <UCard>
      <template #header>
        <h3>Создать пользователя</h3>
      </template>

      <UForm :schema="schema" :state="state" @submit="onSubmit">
        <UFormField label="Имя" name="name">
          <UInput v-model="state.name" />
        </UFormField>

        <template #footer>
          <div class="flex justify-end gap-2">
            <UButton variant="ghost" @click="isOpen = false">
              Отмена
            </UButton>
            <UButton type="submit" :loading="loading">
              Создать
            </UButton>
          </div>
        </template>
      </UForm>
    </UCard>
  </UModal>
</template>

Чек-лист

  • Используется UForm с schema (Zod)
  • Каждое поле в UFormField с label и name
  • Сообщения об ошибках контекстные (на русском)
  • Телефоны и БИН/ИИН имеют маски
  • Кнопка submit имеет loading состояние
  • Обработаны ошибки сервера

Связанные стандарты