vuevaldi

Reactive form validation library for Vue 3 with yup
3,731 2
Install
npm install vuevaldi
Latest Version:0.14.0
Vue:3
Older releases:1 · 2
License:MIT
Last Updated:Aug 21, 2026
Links: GitHub  ·  npm
Maintainer: megaalik

vuevaldi

Библиотека валидации форм для Vue 3.

TypeScript-first, агностик к валидатору (yup работает из коробки), реактивная, событийно-ориентированная.

🌐 Language / Язык: English · Русский


Возможности

  • Агностик к валидатору: подходит любой валидатор, реализующий интерфейс FormValidator; адаптер для yup включён.
  • Ошибки структурно повторяют модель формы (вложенные объекты), внутри — плоские пути.
  • Метки (labels) и значения по умолчанию берутся из самой схемы (yup .label() / .default()).
  • Перевалидация при вводе (validateOnInput) — до submit показываются ошибки только изменённых полей.
  • События submit: success, error, finished.
  • Маппинг серверных ошибок через errorHandler + violations.
  • Написано на TypeScript со строгой типизацией полей, ошибок, меток и дефолтов.

Установка

npm install vuevaldi yup

vue и yup — peer-зависимости. Пакет только ESM, типы включены.


Быстрый старт

Объявляем yup-схему, создаём валидатор через useYupValidator и форму через useForm.

<script setup lang="ts">
import * as yup from 'yup';
import { useForm, useYupValidator } from 'vuevaldi';

const schema = yup.object({
    name: yup.string().required('Имя обязательно').label('Имя'),
    email: yup.string().email('Некорректный email').required('Email обязателен').label('Email'),
    age: yup.number().min(18, 'Должно быть 18+').label('Возраст'),
});

const validator = useYupValidator(schema);

const { model, error, errors, labels, isSubmitting, submit } = useForm({
    validator,
    validateOnInput: true,
    submitHandler: async values => {
        await api.save(values);
    },
});
</script>

<template>
    <form @submit.prevent="submit()">
        <p v-if="error" class="form-error">{{ error }}</p>

        <div>
            <label>{{ labels.name }}</label>
            <input v-model="model.name" />
            <span v-if="errors.name" class="field-error">{{ errors.name[0] }}</span>
        </div>

        <div>
            <label>{{ labels.email }}</label>
            <input v-model="model.email" />
            <span v-if="errors.email" class="field-error">{{ errors.email[0] }}</span>
        </div>

        <div>
            <label>{{ labels.age }}</label>
            <input v-model.number="model.age" />
            <span v-if="errors.age" class="field-error">{{ errors.age[0] }}</span>
        </div>

        <button type="submit" :disabled="isSubmitting">Отправить</button>
    </form>
</template>

model, errors, labels и остальное — Vue-рефы / computed-рефы. При деструктуризации в <script setup> они автоматически разворачиваются в шаблоне. Если держать весь объект целиком (const form = useForm(...)), обращайтесь через .value: form.model.value.name.

submit() сначала валидирует; при успехе вызывает submitHandler(values) и возвращает true, иначе — false.


API — useForm(opt)

Поля FormContextOptions (все опциональны, кроме validator и submitHandler):

Опция Тип Описание
values PartialObjectDeep<TFields> Реальные начальные данные (например, загруженная запись). Используются как есть, без merge со schema-defaults.
defaultValues PartialObjectDeep<TFields> Шаблон начального/сброшенного состояния. Глубоко сливается поверх schema-defaults (schema дозаполняет, эти переопределяют).
extraData TExtraData Произвольные дополнительные данные, доступные через getExtraData().
submitHandler (values: TFields) => TResp | Promise<TResp> Обязателен. Вызывается при успешной валидации.
errorHandler (error: TErr) => { message: string; violations?: Violation[] } Преобразует ошибку submit в сообщение и ошибки полей.
resetAfterSubmit boolean Сбросить форму после успешного submit.
validateOnInput boolean Перевалидировать при изменении модели; до первого submit показываются ошибки только изменённых полей.
validator FormValidator<TFields> Обязателен. Реализация валидатора.
type Violation = { message: string; propertyPath: string };

Приоритет начальной модели:

values ?? merge(schemaDefaults, defaultValues) ?? {}
  • values выигрывают целиком (schema не подмешивается — реальные данные не трогаются).
  • Иначе база — schema-defaults, поверх глубоко сливается defaultValues.
  • Массивы заменяются целиком, не сливаются по индексу.

API — возвращаемый контекст

Поле Тип Описание
model Ref<PartialObjectDeep<TFields>> Реактивное состояние формы; биндьте инпуты через v-model.
error ComputedRef<string> Глобальная ошибка формы (submit / сервер).
errors ComputedRef<ValidationErrors<TFields>> Ошибки полей, структурно повторяющие модель.
labels ComputedRef<FormLabels<TFields>> Метки полей из схемы, структурно повторяющие модель.
isSubmitting ComputedRef<boolean> true, пока идёт submit().
isDirty ComputedRef<boolean> true, если модель отличается от исходного состояния (на момент создания формы или последнего reset()). Сравнение всегда идёт по текущим данным, поэтому ручной возврат значений к исходным (в т.ч. по отдельному полю) тоже сбрасывает флаг в false — не только reset().
submit (throwError?: boolean) => Promise<boolean> Валидация → вызов submitHandler. Возвращает успех. С throwError бросает Error(error) при неудаче.
reset (newOpt?: { values?, defaultValues? }) => void Сброс ошибок и модели (с merge schema-defaults как выше).
validate () => Promise<false | TFields> Валидирует текущую модель; возвращает распарсенные значения или false.
addEventListener (event, listener) => void Подписка на success / error / finished.
getExtraData () => TExtraData Возвращает extraData; бросает ошибку, если не задано.

AnyFormContext — слаботипизированная версия того же контракта (для передачи контекста куда-либо без типов).


Валидатор

Интерфейс FormValidator

Любой валидатор для useForm должен реализовать этот контракт:

interface FormValidator<TFields extends Recordable = Recordable> {
    isValid: (values: PartialObjectDeep<TFields>) => Promise<boolean>;
    parse: (values: PartialObjectDeep<TFields>) => Promise<
        | { isError: false; values: TFields; errors: undefined }
        | { isError: true; values: undefined; errors: FlattenedErrors }
    >;
    describe?: () => FormSchemaInfo<TFields>;
}
  • errors — плоская карта: Record<string, string[]> (пути вида 'address.city'). Форма разворачивает её во вложенный реф errors.
  • describe опционален и питает labels + schema-defaults.
  • FlattenedErrors = Record<string, string[]>.

useYupValidator(schema, options?)

Оборачивает yup ObjectSchema в FormValidator.

import { useYupValidator } from 'vuevaldi';

const schema = yup.object({
    name: yup.string().required().label('Имя'),
    age: yup.number().min(18).label('Возраст'),
});

const validator = useYupValidator(schema, {
    // ValidateOptions (yup): strict, abortEarly, stripUnknown, recursive...
});

Дефолты: strict: false, abortEarly: false, stripUnknown: false, recursive: true, disableStackTrace: true.

Работает со вложенными объектами, массивами, кортежами, .when() и т.д. — ошибки раскладываются по путям полей и затем собираются обратно в структуру модели.

const schema = yup.object({
    profile: yup.object({
        firstName: yup.string().required().label('Имя'),
        lastName: yup.string().required().label('Фамилия'),
    }),
    tags: yup.array().of(yup.string()),
    items: yup.array().of(yup.object({
        sku: yup.string().required().label('Артикул'),
        qty: yup.number().min(1).label('Количество'),
    })),
});

errors.profile.firstName → string[], errors.items → [{ sku?: string[]; qty?: string[] }].


Метки и дефолты из схемы

useYupValidator отдаёт describe(), построенный из yup schema.describe(), который наполняет две вещи:

  • labels — метки из .label(), структурно как errors/модель:

    • поля-листья → string | undefined;
    • поля-объекты → вложенный объект меток; собственный label объекта доступен по зарезервированному ключу $label (labels.money.$label);
    • массивы примитивов → метка элемента (с фолбэком на метку массива);
    • массивы объектов → массив с одним элементом = метки элемента (labels.items[0].sku); метка самого массива лежит на массиве (labels.items.$label), метка объекта-элемента — labels.items[0].$label.
  • defaults — значения из .default(), база для начальной модели и reset() (сливаются под defaultValues, см. выше).

const schema = yup.object({
    name: yup.string().default('Иван').label('Имя'),
    address: yup.object({
        city: yup.string().default('Москва').label('Город'),
    }).label('Адрес'),
});

const { labels, model } = useForm({
    validator: useYupValidator(schema),
    submitHandler: async () => {},
});

// labels: { name: 'Имя', address: { $label: 'Адрес', city: 'Город' } }
// labels.address.$label → 'Адрес'
// model:  { name: 'Иван', address: { city: 'Москва' } }

Кастомные валидаторы могут реализовать describe?: () => FormSchemaInfo<TFields>, чтобы тоже отдавать labels и defaults.


События

Подписка через addEventListener; каждый вызов регистрирует ещё один слушатель.

form.addEventListener('success', data => console.log('Сохранено', data));
form.addEventListener('error', err => console.error('Ошибка', err));
form.addEventListener('finished', () => console.log('Готово'));
Событие Payload Когда
success TResp — возвращаемое значение submitHandler После успешного submit
error TErr — ошибка валидации ('Validation failed') или ошибка из submitHandler При неудачной валидации или submit
finished — Всегда после попытки submit

Ошибки при submit

submit() сначала валидирует. При неудачной валидации error получает 'Validation failed', срабатывает событие error, submit() возвращает false. Если валидация прошла — ожидается submitHandler(values):

  • при успехе — событие success (+ сброс, если resetAfterSubmit), возвращается true;
  • при ошибке — вызывается errorHandler(error) (если передан), иначе error.value = error.message для инстансов Error.

errorHandler преобразует ошибку в сообщение и ошибки полей, обычно из ответа бэкенда:

import type { AxiosError } from 'axios';

const schema = yup.object({
    name: yup.string().required().label('Имя'),
    email: yup.string().required().label('Email'),
});

type UserForm = yup.InferType<typeof schema>;

interface ApiError {
    message: string;
    errors: { field: string; message: string }[];
}

const form = useForm<UserForm, void, AxiosError<ApiError>>({
    validator: useYupValidator(schema),
    submitHandler: values => api.save(values),
    errorHandler: e => ({
        message: e.response?.data.message ?? 'Запрос не удался',
        violations: e.response?.data.errors.map(v => ({
            message: v.message,
            propertyPath: v.field, // например 'name' или 'address.city'
        })),
    }),
});

Violations с propertyPath попадают в ту же вложенную структуру errors, что и ошибки клиентской валидации.


Опции подробнее

validateOnInput

При включении форма глубоко следит за моделью, перевалидирует при каждом изменении и до первого submit показывает ошибки только изменённых полей. После неудачного submit показываются все ошибки.

resetAfterSubmit

Сбрасывает форму (ошибки + модель к дефолтам) после успешного вызова submitHandler.

extraData / getExtraData

Произвольные метаданные, переданные при создании и читаемые через getExtraData() (типизированы как TExtraData). Бросает ошибку, если extraData не передан.

values vs defaultValues

  • values — реальные данные, используются как есть, без merge со schema-defaults.
  • defaultValues — шаблон, глубоко сливается поверх schema-defaults.
  • Не передавать ничего — форма инициализируется только из schema-defaults.

Кастомный валидатор

Подойдёт любой объект, соответствующий FormValidator. Минимальный ручной валидатор:

import type { FormValidator } from 'vuevaldi';

type UserForm = {
    name: string;
    age?: number;
};

const customValidator: FormValidator<UserForm> = {
    isValid: async values => Boolean(values.name && values.name.length >= 2),

    parse: async values => {
        const errors: Record<string, string[]> = {};

        if (!values.name) {
            errors.name = ['Имя обязательно'];
        } else if (values.name.length < 2) {
            errors.name = ['Имя должно быть не короче 2 символов'];
        }

        if (values.age !== undefined && values.age < 18) {
            errors.age = ['Должно быть 18+'];
        }

        if (Object.keys(errors).length > 0) {
            return { isError: true, values: undefined, errors };
        }

        return { isError: false, values: values as UserForm, errors: undefined };
    },
};

const form = useForm<UserForm>({
    validator: customValidator,
    submitHandler: values => api.save(values),
});

Опционально реализуйте describe(), чтобы тоже отдавать labels и schema-defaults:

import type { FormSchemaInfo } from 'vuevaldi';

const customValidator: FormValidator<UserForm> = {
    // isValid, parse ...

    describe: (): FormSchemaInfo<UserForm> => ({
        labels: { name: 'Имя', age: 'Возраст' },
        defaults: { name: 'Иван' },
    }),
};

Экспортируемые типы

import type {
    AnyFormContext,     // слаботипизированный контекст
    FormContext,        // типизированный контекст
    FormContextOptions, // опции useForm
    FormSchemaInfo,     // { labels?, defaults? } из validator.describe
    FormValidator,      // контракт валидатора
    FormLabels,         // структура меток
    PartialObjectDeep,  // глубокий partial полей (массивы не рекурсятся)
    ValidationErrors,   // структура ошибок
} from 'vuevaldi';

Лицензия

MIT © ТОО «ADEGARA».

Related Packages

@retailcrm/vue-formulario

Vue Formulario is a library, inspired by Vue Formulate , that handles the core l...

86,689 3
@braid/vue-formulate

The easiest way to build forms in Vue.

451,602 2,238
vue-yup-form

Headless form validation with Vue and Yup

17,480 30
vue-formulate

The easiest way to build forms in Vue with validation and vuex support.

4,367 7
@vueform/core

Headless, schema-driven form framework core for Vue 3 — elements, validation, co...

136 0

Version History

Version Released Vue License
0.14.0 ^3.0.0 MIT
0.13.3 ^3.0.0 MIT
0.13.2 ^3.0.0 MIT
0.13.1 ^3.0.0 MIT
0.13.0 ^3.0.0 MIT