Реактивные формы из декларативной схемы (JSON, Zod, Yup или Valibot) для Vue 3 — headless и SSR-совместимая альтернатива VeeValidate/FormKit.

Бэкенд уже описывает форму в JSON Schema, OpenAPI или Zod-типе — vue-form-schema превращает это описание прямо в рабочую форму на экране, вместо того чтобы вручную дублировать те же поля и правила валидации во Vue-компоненте.
Выбор «юрлицо» открывает поле ИНН, а «физлицо» — паспортные данные, и оба варианта нельзя показывать одновременно — форма сама решает, какие поля показать и провалидировать, в зависимости от того, что уже выбрал пользователь.
Список телефонов, позиций в счёте или участников команды — пользователь добавляет и удаляет строки сам, и каждая новая строка валидируется и очищается так же, как и первая.
Анкета на десять экранов пугает, если показать её целиком, — форма делится на шаги с собственной проверкой каждого, и следующий шаг открывается только после того, как пройден предыдущий.
Email уже занят — это можно узнать только после отправки на сервер. Ответ бэкенда раскладывается по нужным полям формы одним вызовом, и именно поле email подсвечивается красным, а не общее «что-то пошло не так» наверху.

Создавайте формы из FieldDefinition[], JSON, стандартной JSON Schema/OpenAPI, Zod, Yup или Valibot. Адаптеры для каждой библиотеки поставляются отдельными entry points, не раздувая основной бандл. Вывод типов (InferValues) работает автоматически для всех источников, давая полную типобезопасность.

Управляйте видимостью и доступностью полей через булевы значения, реактивные функции или строковые выражения. Опции select/radio могут быть синхронными или асинхронными с автоматическим перезапросом при изменении зависимостей (optionsDeps). Всё обновляется реактивно без лишних ререндеров.

Встроенные синхронные и асинхронные валидаторы (required, minLength, email, sameAs, fileType и другие) с режимами validateOn (input/blur/submit/eager) и validateMode (first/all). Маски ввода с пресетами (телефон, дата, IBAN, ИНН) и кастомными паттернами применяются автоматически.

useFieldArray даёт полный контроль над динамическими списками: append, prepend, remove, move, swap, replace. useMultiStepForm управляет пошаговыми формами с валидацией каждого шага и общим submit-обработчиком. Готовые рендереры (ArrayField, MultiStepFormRenderer) ускоряют разработку.

Core не зависит от UI — используйте любой дизайн через кастомные компоненты или реестр. Готовые рендереры для Tailwind, shadcn/ui, PrimeVue и Naive UI. SSR-безопасность, персистентность (local/sessionStorage), DevTools-инспектор и Nuxt-модуль с автоимпортами.

Собирайте большие формы из переиспользуемых частей через mergeSchemas, omitFields, pickFields и extendField — без дублирования полей между похожими формами. discriminatedFields подключает разные наборы полей по значению другого поля, а applyServerErrors раскладывает ответ бэкенда по полям формы одним вызовом.
Разбираем обычную Zod-схему в поля формы через parseZod() — типы values выводятся из z.infer<> автоматически, без useForm<Values>().
import { z } from 'zod'
import { parseZod } from '@macrulez/vue-form-schema/zod'
import { useForm } from '@macrulez/vue-form-schema'
const schema = z.object({
name: z.string().min(2).describe('Full name'),
age: z.number().min(0).optional(),
email: z.string().email(),
role: z.enum(['admin', 'user']),
})
const fields = parseZod(schema)
const { values } = useForm({ schema: fields })
// values.value.name is string, values.value.age is number | undefined, ...
// — inferred automatically from `schema` via z.infer<typeof schema>, no
// useForm<Values>(...) needed.visible принимает функцию от текущих значений — поле рендерится и валидируется только когда условие истинно, а clearOnHide сбрасывает его при скрытии.
import type { FieldDefinition } from '@macrulez/vue-form-schema'
import { useForm } from '@macrulez/vue-form-schema'
const schema: FieldDefinition[] = [
{ type: 'checkbox', name: 'hasCompany', label: 'I represent a company' },
{
type: 'text',
name: 'companyName',
label: 'Company name',
visible: (values) => values['hasCompany'] === true,
required: true,
},
]
const { values } = useForm({ schema, clearOnHide: true })applyServerErrors сам разбирает ответ Laravel/DRF (или любой формат через свой маппер) и раскладывает ошибки по полям — руками парсить ответ под каждый бэкенд не нужно.
import { applyServerErrors } from '@macrulez/vue-form-schema'
const res = await fetch('/api/users', { method: 'POST', body: JSON.stringify(form.values.value) })
if (!res.ok) {
const { formErrors } = applyServerErrors(form, await res.json(), { format: 'laravel' })
if (formErrors.length) toast.error(formErrors[0]) // errors not tied to a field
}
// Built-in formats: 'laravel', 'drf', 'flat' — or pass your own mapper.
// The next client-side validation naturally replaces a stale server error.