Skip to content

Form ​

Обёртка формы с раскладкой, валидацией и управлением состоянием элементов формы.

Optional short bio
vue
<script setup>
const formRef = ref()
const form = reactive({ name: '', email: '', bio: '' })

const rules = {
  name: [
    { required: true, message: 'Please enter your name' },
    { min: 2, max: 30, message: 'Name must be 2-30 characters' },
  ],
  email: [
    { required: true, message: 'Please enter your email' },
    { type: 'email', message: 'Please enter a valid email' },
  ],
}

async function onSubmit() {
  const { valid } = await formRef.value.validate()
  if (valid) { /* submit */ }
}
</script>

<template>
  <VxForm ref="formRef" :model="form" :rules="rules">
    <VxFormItem label="Name" field="name">
      <VxInput v-model="form.name" />
    </VxFormItem>
    <VxFormItem label="Email" field="email">
      <VxInput v-model="form.email" />
    </VxFormItem>
    <VxFormItem>
      <VxButton @click="onSubmit">Submit</VxButton>
    </VxFormItem>
  </VxForm>
</template>

Horizontal Layout ​

vue
<template>
  <VxForm :model="form" :rules="rules" layout="horizontal" :label-width="120">
    <VxFormItem label="Username" field="username">
      <VxInput v-model="form.username" />
    </VxFormItem>
    <VxFormItem label="Password" field="password">
      <VxInput v-model="form.password" type="password" />
    </VxFormItem>
    <VxFormItem label=" ">
      <VxButton>Login</VxButton>
    </VxFormItem>
  </VxForm>
</template>

Inline Layout ​

All
vue
<template>
  <VxForm :model="form" layout="inline">
    <VxFormItem label="Search">
      <VxInput v-model="form.search" />
    </VxFormItem>
    <VxFormItem label="Category">
      <VxSelect v-model="form.category" :options="options" />
    </VxFormItem>
    <VxFormItem>
      <VxButton>Search</VxButton>
    </VxFormItem>
  </VxForm>
</template>

Label Description ​

Добавляет иконку подсказки рядом с меткой через проп description или слот #description для форматированного содержимого.

vue
<template>
  <!-- Simple text description -->
  <VxFormItem label="API Key" description="Your unique API key from the dashboard.">
    <VxInput v-model="form.apiKey" />
  </VxFormItem>

  <!-- Rich content via #description slot -->
  <VxFormItem label="Secret">
    <template #description>
      <div>
        <strong>Signing Secret</strong>
        <p>Used to verify webhook payloads.</p>
      </div>
    </template>
    <VxInput v-model="form.secret" type="password" />
  </VxFormItem>
</template>

Type Validators ​

Встроенные валидаторы типов: email, url, number, integer, float, date, hex, ip, phone, alpha, alphanumeric, string, boolean, array.

vue
const rules = {
  email: [
    { required: true, message: 'Required' },
    { type: 'email', message: 'Invalid email format' },
  ],
  url: [{ type: 'url', message: 'Invalid URL' }],
  age: [
    { type: 'integer', message: 'Must be a whole number' },
    { min: 1, max: 150, message: 'Age must be 1-150' },
  ],
  ip: [{ type: 'ip', message: 'Invalid IP address' }],
  hex: [{ type: 'hex', message: 'Invalid hex color' }],
  phone: [{ type: 'phone', message: 'Invalid phone number' }],
}

String Constraints ​

Используйте pattern, enum, startsWith, endsWith, contains и типы alpha/alphanumeric для валидации строк.

Format: XX-000 or XXX-0000
Letters and numbers only
Must start with #
Must end with .com
admin, editor, or viewer
vue
const rules = {
  code: [
    { required: true, message: 'Required' },
    { pattern: /^[A-Z]{2,3}-\d{3,4}$/, message: 'Format: XX-000 or XXX-0000' },
  ],
  slug: [
    { type: 'alphanumeric', message: 'Only letters and numbers' },
  ],
  tag: [
    { startsWith: '#', message: 'Must start with #' },
    { min: 2, message: 'At least 2 characters' },
  ],
  domain: [
    { endsWith: '.com', message: 'Must end with .com' },
  ],
  role: [
    { enum: ['admin', 'editor', 'viewer'], message: 'Must be admin, editor, or viewer' },
  ],
}

Cross-Field Validation ​

Используйте same и different для сравнения полей, а contains — для проверки вхождения подстроки.

vue
const rules = {
  password: [
    { required: true, message: 'Password is required' },
    { min: 8, message: 'At least 8 characters' },
    { contains: '!', message: 'Must contain !' },
  ],
  confirm: [
    { required: true, message: 'Please confirm' },
    { same: 'password', message: 'Passwords do not match' },
  ],
  newEmail: [
    { required: true, message: 'New email is required' },
    { type: 'email', message: 'Invalid email' },
    { different: 'currentEmail', message: 'Must differ from current email' },
  ],
}

Async Validator ​

Валидаторы могут возвращать Promise. Удобно для серверных проверок, например доступности имени пользователя.

Try 'admin', 'root', or 'test'
vue
const rules = {
  username: [
    { required: true, message: 'Username is required' },
    { type: 'alpha', message: 'Letters only' },
    { min: 3, max: 20, message: '3-20 characters' },
    {
      validator: async (value) => {
        const taken = await checkUsername(value)
        if (taken) return 'Username is already taken'
        return true
      },
      trigger: 'blur',
    },
  ],
}

Reset & Clear Validation ​

vue
<script setup>
const formRef = ref()
</script>

<template>
  <VxForm ref="formRef" :model="form" :rules="rules">
    ...
    <VxButton @click="formRef.validate()">Validate</VxButton>
    <VxButton @click="formRef.clearValidation()">Clear Errors</VxButton>
    <VxButton @click="formRef.resetFields()">Reset</VxButton>
  </VxForm>
</template>

Disabled ​

vue
<template>
  <VxForm :model="form" disabled>
    <VxFormItem label="Name"><VxInput v-model="form.name" /></VxFormItem>
    <VxFormItem label="Email"><VxInput v-model="form.email" /></VxFormItem>
  </VxForm>
</template>

Horizontal Example ​

Select role
vue
<script setup>
const formRef = ref()
const form = reactive({
  firstName: '', lastName: '', email: '',
  role: '', newsletter: false, notes: '',
})

const rules = {
  firstName: { required: true, message: 'First name is required' },
  lastName: { required: true, message: 'Last name is required' },
  email: [
    { required: true, message: 'Email is required' },
    { type: 'email', message: 'Invalid email format' },
  ],
  role: { required: true, message: 'Please select a role' },
}
</script>

<template>
  <VxForm ref="formRef" :model="form" :rules="rules" layout="horizontal" :label-width="120">
    <VxFormItem label="First Name" field="firstName">
      <VxInput v-model="form.firstName" />
    </VxFormItem>
    <VxFormItem label="Role" field="role">
      <VxSelect v-model="form.role" :options="roles" />
    </VxFormItem>
    <VxFormItem label="Newsletter">
      <VxSwitch v-model="form.newsletter" />
    </VxFormItem>
    <VxFormItem label=" ">
      <VxButton @click="formRef.validate()">Submit</VxButton>
    </VxFormItem>
  </VxForm>
</template>

Registration Form ​

Полный пример с несколькими правилами валидации на поле, проверкой надёжности пароля, сравнением между полями и разными типами ввода.

Optional
HTTPS required
Select role
Max 200 characters
vue
<script setup>
const formRef = ref()
const form = reactive({
  name: '', email: '', password: '', confirmPassword: '',
  phone: '', website: '', role: '', bio: '', terms: false,
})

const rules = {
  name: [
    { required: true, message: 'Full name is required' },
    { min: 2, max: 50, message: '2-50 characters' },
    { type: 'alpha', message: 'Letters only' },
  ],
  email: [
    { required: true, message: 'Email is required' },
    { type: 'email', message: 'Enter a valid email address' },
  ],
  password: [
    { required: true, message: 'Password is required' },
    { min: 8, message: 'At least 8 characters' },
    {
      validator: (v) => {
        if (!/[A-Z]/.test(v)) return 'Must contain an uppercase letter'
        if (!/[0-9]/.test(v)) return 'Must contain a number'
        if (!/[^a-zA-Z0-9]/.test(v)) return 'Must contain a special character'
        return true
      },
    },
  ],
  confirmPassword: [
    { required: true, message: 'Confirm your password' },
    { same: 'password', message: 'Passwords do not match' },
  ],
  phone: [{ type: 'phone', message: 'Enter a valid phone number' }],
  website: [
    { type: 'url', message: 'Enter a valid URL' },
    { startsWith: 'https://', message: 'Must use HTTPS' },
  ],
  role: [
    { required: true, message: 'Select a role' },
  ],
  bio: [{ max: 200, message: 'Max 200 characters' }],
  terms: [
    { validator: (v) => v === true ? true : 'You must accept the terms' },
  ],
}
</script>

<template>
  <VxForm ref="formRef" :model="form" :rules="rules">
    <VxFormItem label="Full Name" field="name">
      <VxInput v-model="form.name" />
    </VxFormItem>
    <VxFormItem label="Email" field="email">
      <VxInput v-model="form.email" />
    </VxFormItem>
    <VxFormItem label="Password" field="password">
      <VxInput v-model="form.password" type="password" />
    </VxFormItem>
    <VxFormItem label="Confirm Password" field="confirmPassword">
      <VxInput v-model="form.confirmPassword" type="password" />
    </VxFormItem>
    <VxFormItem label="Phone" field="phone" help="Optional">
      <VxInput v-model="form.phone" />
    </VxFormItem>
    <VxFormItem label="Website" field="website" help="HTTPS required">
      <VxInput v-model="form.website" />
    </VxFormItem>
    <VxFormItem label="Role" field="role">
      <VxSelect v-model="form.role" :options="roles" />
    </VxFormItem>
    <VxFormItem label="Bio" field="bio" help="Max 200 characters">
      <VxInput v-model="form.bio" type="textarea" :rows="3" />
    </VxFormItem>
    <VxFormItem field="terms">
      <VxCheckbox v-model="form.terms" label="I accept the terms and conditions" />
    </VxFormItem>
    <VxFormItem>
      <VxButton @click="formRef.validate()">Register</VxButton>
    </VxFormItem>
  </VxForm>
</template>

Nested Model ​

Используйте точечную нотацию в field, чтобы связывать и валидировать вложенные свойства объекта, например user.username.

Optional, max 200 characters
vue
<script setup>
import { ref, reactive } from 'vue'

const formRef = ref()
const form = reactive({
  user: { username: '', password: '' },
  description: '',
})

// Use dot-notation keys to match nested field paths
const rules = {
  'user.username': [
    { required: true, message: 'Username is required' },
    { min: 3, max: 20, message: '3-20 characters' },
    { type: 'alphanumeric', message: 'Letters and numbers only' },
  ],
  'user.password': [
    { required: true, message: 'Password is required' },
    { min: 8, message: 'At least 8 characters' },
    { pattern: /[A-Z]/, message: 'Needs an uppercase letter' },
    { pattern: /[0-9]/, message: 'Needs a number' },
  ],
  description: [
    { max: 200, message: 'Max 200 characters' },
  ],
}

async function onSubmit() {
  const { valid } = await formRef.value.validate()
  if (valid) {
    console.log('Submitted:', JSON.parse(JSON.stringify(form)))
  }
}
</script>

<template>
  <VxForm ref="formRef" :model="form" :rules="rules">
    <VxFormItem label="Username" field="user.username">
      <VxInput v-model="form.user.username" placeholder="Letters and numbers, 3-20 chars" />
    </VxFormItem>
    <VxFormItem label="Password" field="user.password">
      <VxInput v-model="form.user.password" type="password" placeholder="8+ chars, uppercase, number" />
    </VxFormItem>
    <VxFormItem label="Description" field="description" help="Optional, max 200 characters">
      <VxInput v-model="form.description" type="textarea" :rows="3" />
    </VxFormItem>
    <VxFormItem>
      <VxButton @click="onSubmit">Submit</VxButton>
      <VxButton variant="outline" @click="formRef.resetFields()">Reset</VxButton>
    </VxFormItem>
  </VxForm>
</template>

Status Icons ​

Показывайте иконки состояния валидации (спиннер, галочка, крестик) рядом с полем ввода.

vue
<template>
  <VxForm ref="formRef" :model="form" :rules="rules" :status-icon="true">
    <VxFormItem label="Email" field="email">
      <VxInput v-model="form.email" />
    </VxFormItem>
  </VxForm>
</template>

Label Suffix ​

Добавляйте символ-суффикс (например, :) ко всем меткам.

vue
<template>
  <VxForm :model="form" label-suffix=":">
    <VxFormItem label="Name"><VxInput v-model="form.name" /></VxFormItem>
    <VxFormItem label="Age"><VxInput v-model="form.age" /></VxFormItem>
  </VxForm>
</template>

Scroll to Error ​

Автоматически прокручивать к первому полю с ошибкой после валидации.

vue
<template>
  <VxForm ref="formRef" :model="form" :rules="rules" :scroll-to-error="true">
    <!-- fields -->
    <VxButton @click="formRef.validate()">Validate All</VxButton>
  </VxForm>
</template>

Show All Errors ​

Показывать все нарушения правил сразу, вместо того чтобы останавливаться на первой ошибке.

vue
<template>
  <VxForm :model="form" :rules="rules" :show-all-errors="true">
    <VxFormItem label="Password" field="password">
      <VxInput v-model="form.password" />
    </VxFormItem>
  </VxForm>
</template>

<script setup>
const rules = {
  password: [
    { required: true, message: 'Password is required' },
    { min: 8, message: 'At least 8 characters' },
    { pattern: /[A-Z]/, message: 'Must contain an uppercase letter' },
    { pattern: /[0-9]/, message: 'Must contain a number' },
    { pattern: /[^a-zA-Z0-9]/, message: 'Must contain a special character' },
  ],
}
</script>

Loading State ​

Установите loading, чтобы отключить все взаимодействия и показать состояние загрузки. Сочетается с disabled.

vue
<script setup>
const loading = ref(false)
async function onSubmit() {
  loading.value = true
  await submitForm()
  loading.value = false
}
</script>

<template>
  <VxForm :model="form" :loading="loading">
    <!-- fields -->
    <VxButton @click="onSubmit">Submit</VxButton>
  </VxForm>
</template>

Dirty Tracking ​

Отслеживайте, была ли форма изменена. Получайте isDirty и dirtyFields через ref.

No changes
vue
<script setup>
const formRef = ref()
const form = reactive({ name: 'John', email: 'john@test.com' })
</script>

<template>
  <VxForm ref="formRef" :model="form">
    <!-- fields -->
    <VxButton :disabled="!formRef?.isDirty">Save Changes</VxButton>
    <span>{{ formRef?.isDirty ? `Modified: ${formRef?.dirtyFields}` : 'No changes' }}</span>
  </VxForm>
</template>

Dependent Field Re-validation ​

Используйте проп dependencies у FormItem, чтобы повторно валидировать поле при изменении другого. Полезно для сценариев вроде «подтвердите пароль».

vue
<template>
  <VxForm :model="form" :rules="rules">
    <VxFormItem label="Password" field="password">
      <VxInput v-model="form.password" type="password" />
    </VxFormItem>
    <VxFormItem label="Confirm" field="confirm" :dependencies="['password']">
      <VxInput v-model="form.confirm" type="password" />
    </VxFormItem>
  </VxForm>
</template>

<script setup>
const rules = {
  password: [{ required: true }, { min: 6, message: 'At least 6 characters' }],
  confirm: [{ required: true }, { same: 'password', message: 'Passwords do not match' }],
}
</script>

Required Mark Position ​

Управляйте тем, где появляется звёздочка обязательного поля — слева или справа от метки.

Слева (по умолчанию)
Справа
vue
<template>
  <!-- Asterisk on left (default) -->
  <VxForm :model="form" :rules="rules" required-mark-position="left">...</VxForm>

  <!-- Asterisk on right -->
  <VxForm :model="form" :rules="rules" required-mark-position="right">...</VxForm>
</template>

Extra & Error Slots ​

Используйте #extra для подсказок под полем ввода и #error для собственной отрисовки ошибок.

Must be 8+ chars with uppercase, number, and special character.
vue
<template>
  <VxFormItem label="Password" field="password">
    <VxInput v-model="form.password" type="password" />
    <template #extra>
      <span>Must be 8+ chars with uppercase, number, and special character.</span>
    </template>
    <template #error="{ errors, first }">
      <ul>
        <li v-for="e in errors" :key="e">{{ e }}</li>
      </ul>
    </template>
  </VxFormItem>
</template>

Props ​

Form ​

PropTypeDefaultDescription
modelRecord<string, any>—Объект данных формы
rulesFormRules—Правила валидации по имени поля
layout'vertical' | 'horizontal' | 'inline''vertical'Раскладка формы
labelWidthstring | number—Ширина метки для горизонтальной раскладки
labelAlign'left' | 'right' | 'top''right'Выравнивание текста метки в горизонтальной раскладке
labelSuffixstring''Суффикс, добавляемый ко всем меткам (например, :)
size'sm' | 'default' | 'lg''default'Размер формы
disabledbooleanfalseОтключить всю форму
loadingbooleanfalseСостояние загрузки — блокирует события указателя
showRequiredMarkbooleantrueПоказывать красную звёздочку у обязательных полей
requiredMarkPosition'left' | 'right''left'Позиция звёздочки обязательного поля
validateOnChangebooleantrueАвтовалидация при изменении модели
validateOnBlurbooleantrueАвтовалидация при потере фокуса
hideErrorMessagebooleanfalseСкрыть все сообщения об ошибках
scrollToErrorbooleanfalseПрокрутить к первой ошибке после валидации
statusIconbooleanfalseПоказывать иконки состояния валидации рядом с полями
showAllErrorsbooleanfalseПоказывать все ошибки валидации, а не только первую

FormItem ​

PropTypeDefaultDescription
labelstring''Текст метки
fieldstring''Имя поля (должно совпадать с ключом в model и rules)
rulesFormRule | FormRule[]—Правила уровня поля (объединяются с правилами формы)
requiredboolean—Переопределить обязательность (определяется из правил автоматически)
labelWidthstring | number—Переопределить ширину метки, заданную на форме
showMessagebooleantrueПоказывать сообщение об ошибке для этого поля
helpstring''Текст-подсказка под полем
descriptionstring''Текст подсказки, показываемый через иконку рядом с меткой
dependenciesstring[]—Повторно валидировать это поле при изменении перечисленных

Events ​

EventPayloadDescription
validate(field, valid, message)Срабатывает после валидации поля
submitEventНативная отправка формы (по умолчанию предотвращается)

Exposed Methods ​

MethodSignatureDescription
validate() => Promise<{ valid, errors }>Валидировать все поля
validateFields(fields: string[]) => Promise<{ valid, errors }>Валидировать конкретные поля
resetFields() => voidСбросить состояние валидации
clearValidation(fields?: string[]) => voidОчистить сообщения об ошибках
isDirtyComputedRef<boolean>Было ли изменено хоть одно поле
dirtyFieldsComputedRef<string[]>Список имён изменённых полей
scrollToFirstError(field: string) => voidПрокрутить к указанному полю

Slots ​

FormItem ​

SlotPropsDescription
default—Элемент управления формы
label—Своё содержимое метки
label-suffix—Содержимое после текста метки
description—Форматированное содержимое подсказки (заменяет проп description)
extra—Дополнительное содержимое под полем ввода
error{ errors: string[], first: string }Пользовательская отрисовка сообщений об ошибках

FormRule ​

PropertyTypeDescription
requiredbooleanПоле должно иметь значение
messagestringПользовательское сообщение об ошибке
trigger'blur' | 'change' | ('blur' | 'change')[]Когда запускать валидацию
minnumberМинимальная длина/значение
maxnumberМаксимальная длина/значение
lennumberТочная длина
patternRegExpРегулярное выражение для сопоставления
typeFormRuleTypeВстроенный валидатор типа (см. ниже)
validator(value, rule) => boolean | string | PromiseПользовательская функция-валидатор
whitespacebooleanСчитать строки из одних пробелов пустыми
enumany[]Значение должно быть одним из перечисленных
notanyЗначение не должно равняться этому
samestringДолжно совпадать со значением другого поля
differentstringДолжно отличаться от значения другого поля
containsstringДолжно содержать эту подстроку
startsWithstringДолжно начинаться с этой строки
endsWithstringДолжно заканчиваться этой строкой

Type Validators ​

TypeDescription
stringДолжно быть строкой
numberДолжно быть числом
integerДолжно быть целым числом
floatДолжно быть дробным числом
booleanДолжно быть булевым значением
arrayДолжно быть массивом
emailКорректный email-адрес
urlКорректный URL
dateСтрока, которую можно разобрать как дату
hexКорректный hex-цвет (#fff, #ff5733)
ipКорректный IPv4-адрес
phoneКорректный формат номера телефона
alphaТолько буквы (a-z, A-Z)
alphanumericТолько буквы и цифры