Form
Обёртка формы с раскладкой, валидацией и управлением состоянием элементов формы.
<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
<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
<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 для форматированного содержимого.
<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.
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 для валидации строк.
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 — для проверки вхождения подстроки.
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. Удобно для серверных проверок, например доступности имени пользователя.
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
<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
<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
<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
Полный пример с несколькими правилами валидации на поле, проверкой надёжности пароля, сравнением между полями и разными типами ввода.
<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.
<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
Показывайте иконки состояния валидации (спиннер, галочка, крестик) рядом с полем ввода.
<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
Добавляйте символ-суффикс (например, :) ко всем меткам.
<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
Автоматически прокручивать к первому полю с ошибкой после валидации.
<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
Показывать все нарушения правил сразу, вместо того чтобы останавливаться на первой ошибке.
<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.
<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.
<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, чтобы повторно валидировать поле при изменении другого. Полезно для сценариев вроде «подтвердите пароль».
<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
Управляйте тем, где появляется звёздочка обязательного поля — слева или справа от метки.
<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 для собственной отрисовки ошибок.
<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
| Prop | Type | Default | Description |
|---|---|---|---|
model | Record<string, any> | — | Объект данных формы |
rules | FormRules | — | Правила валидации по имени поля |
layout | 'vertical' | 'horizontal' | 'inline' | 'vertical' | Раскладка формы |
labelWidth | string | number | — | Ширина метки для горизонтальной раскладки |
labelAlign | 'left' | 'right' | 'top' | 'right' | Выравнивание текста метки в горизонтальной раскладке |
labelSuffix | string | '' | Суффикс, добавляемый ко всем меткам (например, :) |
size | 'sm' | 'default' | 'lg' | 'default' | Размер формы |
disabled | boolean | false | Отключить всю форму |
loading | boolean | false | Состояние загрузки — блокирует события указателя |
showRequiredMark | boolean | true | Показывать красную звёздочку у обязательных полей |
requiredMarkPosition | 'left' | 'right' | 'left' | Позиция звёздочки обязательного поля |
validateOnChange | boolean | true | Автовалидация при изменении модели |
validateOnBlur | boolean | true | Автовалидация при потере фокуса |
hideErrorMessage | boolean | false | Скрыть все сообщения об ошибках |
scrollToError | boolean | false | Прокрутить к первой ошибке после валидации |
statusIcon | boolean | false | Показывать иконки состояния валидации рядом с полями |
showAllErrors | boolean | false | Показывать все ошибки валидации, а не только первую |
FormItem
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | '' | Текст метки |
field | string | '' | Имя поля (должно совпадать с ключом в model и rules) |
rules | FormRule | FormRule[] | — | Правила уровня поля (объединяются с правилами формы) |
required | boolean | — | Переопределить обязательность (определяется из правил автоматически) |
labelWidth | string | number | — | Переопределить ширину метки, заданную на форме |
showMessage | boolean | true | Показывать сообщение об ошибке для этого поля |
help | string | '' | Текст-подсказка под полем |
description | string | '' | Текст подсказки, показываемый через иконку рядом с меткой |
dependencies | string[] | — | Повторно валидировать это поле при изменении перечисленных |
Events
| Event | Payload | Description |
|---|---|---|
validate | (field, valid, message) | Срабатывает после валидации поля |
submit | Event | Нативная отправка формы (по умолчанию предотвращается) |
Exposed Methods
| Method | Signature | Description |
|---|---|---|
validate | () => Promise<{ valid, errors }> | Валидировать все поля |
validateFields | (fields: string[]) => Promise<{ valid, errors }> | Валидировать конкретные поля |
resetFields | () => void | Сбросить состояние валидации |
clearValidation | (fields?: string[]) => void | Очистить сообщения об ошибках |
isDirty | ComputedRef<boolean> | Было ли изменено хоть одно поле |
dirtyFields | ComputedRef<string[]> | Список имён изменённых полей |
scrollToFirstError | (field: string) => void | Прокрутить к указанному полю |
Slots
FormItem
| Slot | Props | Description |
|---|---|---|
default | — | Элемент управления формы |
label | — | Своё содержимое метки |
label-suffix | — | Содержимое после текста метки |
description | — | Форматированное содержимое подсказки (заменяет проп description) |
extra | — | Дополнительное содержимое под полем ввода |
error | { errors: string[], first: string } | Пользовательская отрисовка сообщений об ошибках |
FormRule
| Property | Type | Description |
|---|---|---|
required | boolean | Поле должно иметь значение |
message | string | Пользовательское сообщение об ошибке |
trigger | 'blur' | 'change' | ('blur' | 'change')[] | Когда запускать валидацию |
min | number | Минимальная длина/значение |
max | number | Максимальная длина/значение |
len | number | Точная длина |
pattern | RegExp | Регулярное выражение для сопоставления |
type | FormRuleType | Встроенный валидатор типа (см. ниже) |
validator | (value, rule) => boolean | string | Promise | Пользовательская функция-валидатор |
whitespace | boolean | Считать строки из одних пробелов пустыми |
enum | any[] | Значение должно быть одним из перечисленных |
not | any | Значение не должно равняться этому |
same | string | Должно совпадать со значением другого поля |
different | string | Должно отличаться от значения другого поля |
contains | string | Должно содержать эту подстроку |
startsWith | string | Должно начинаться с этой строки |
endsWith | string | Должно заканчиваться этой строкой |
Type Validators
| Type | Description |
|---|---|
string | Должно быть строкой |
number | Должно быть числом |
integer | Должно быть целым числом |
float | Должно быть дробным числом |
boolean | Должно быть булевым значением |
array | Должно быть массивом |
email | Корректный email-адрес |
url | Корректный URL |
date | Строка, которую можно разобрать как дату |
hex | Корректный hex-цвет (#fff, #ff5733) |
ip | Корректный IPv4-адрес |
phone | Корректный формат номера телефона |
alpha | Только буквы (a-z, A-Z) |
alphanumeric | Только буквы и цифры |