Skip to content

Поля формы ​

Поля формы отдают строки, а не числа и даты. Поэтому схема зависит от того, что именно кладёт в значение v-model.

Что отдаёт каждое поле ​

ПолеЧто даёт v-modelСхема
<input>, <textarea>'текст' или ''string()
<input> с числом'42' или ''coerce.number()
<input v-model.number>42 или ''number()
<input type="date">'2026-10-02' или ''coerce.date()
<input type="checkbox">true / falseboolean(), согласие — literal(true)
<input type="radio">, <select>значение выбранного вариантаoneOf([...])
<select multiple>, группа чекбоксовмассив значенийarray(oneOf([...]))

Незаполненное поле даёт '', а tinyval считает пустую строку незаполненным значением — см. Пустые значения. Поэтому обязательные поля и optional работают с инпутами без дополнительных настроек.

Пример: анкета ​

vue
<script setup lang="ts">
import { object, string, optional, pipe, coerce, integer, min, minLength, oneOf, array, literal, check } from 'tinyval';
import { useForm } from 'tinyval-vue';

const Profile = object({
  name: string(),
  age: optional(pipe(coerce.number(), integer(), min(14))),
  birthday: pipe(coerce.date(), check((d) => d < new Date())),
  city: oneOf(['moscow', 'spb', 'other']),
  interests: pipe(array(oneOf(['js', 'php', 'go'])), minLength(1)),
  terms: literal(true),
});

const form = useForm(Profile, {
  initial: { name: '', age: '', birthday: '', city: '', interests: [] as string[], terms: false },
});
</script>

<template>
  <input v-model="form.values.name" @blur="form.touch('name')" />
  <input v-model="form.values.age" inputmode="numeric" @blur="form.touch('age')" />
  <input v-model="form.values.birthday" type="date" @blur="form.touch('birthday')" />

  <select v-model="form.values.city" @blur="form.touch('city')">
    <option value="" disabled>—</option>
    <option value="moscow">Moscow</option>
    <option value="spb">Saint Petersburg</option>
    <option value="other">Other</option>
  </select>

  <label v-for="i in ['js', 'php', 'go']" :key="i">
    <input v-model="form.values.interests" type="checkbox" :value="i" @change="form.touch('interests')" /> {{ i }}
  </label>

  <label>
    <input v-model="form.values.terms" type="checkbox" @change="form.touch('terms')" /> I accept the terms
  </label>
</template>

В handleSubmit придут уже приведённые значения: age — число, birthday — Date.

Вложенные поля и массивы ​

Пути ошибок совпадают с путями в values. Для списков используйте useFieldArray — он даёт устойчивые ключи для v-for и не путает ошибки строк при удалении:

vue
<script setup lang="ts">
const items = useFieldArray<{ title: string }>(form, 'items');
</script>

<template>
  <div v-for="field in items.fields.value" :key="field.key">
    <input v-model="field.value.title" :name="`${field.path}.title`" @blur="form.touch(`${field.path}.title`)" />
    <span>{{ form.errors[`${field.path}.title`] }}</span>
    <button type="button" @click="items.remove(field.index)">×</button>
  </div>
  <button type="button" @click="items.append({ title: '' })">+</button>
</template>

Маски ​

Телефоны, карты, суммы и даты удобно вводить с маской — пакет tinyval-mask. Директива v-mask работает на <input>, на <TvField> и на полях UI-библиотек, а снять маску перед проверкой можно в схеме — см. Vue в tinyval-mask.

vue
<!-- the form gets 79991234567, the field shows +7 (999) 123-45-67 -->
<TvField name="phone" v-mask.raw="{ mask: phoneMask, placeholder: '_' }" inputmode="tel" />

Файлы ​

Отдельной схемы для файлов в tinyval пока нет. Файл можно проверить через createSchema или check: <input type="file"> не работает с v-model, поэтому значение кладётся в форму вручную:

vue
<script setup lang="ts">
const avatar = pipe(
  unknown(),
  check((f) => f instanceof File, 'Choose a file'),
  check((f) => (f as File).size <= 2 * 1024 * 1024, 'Max 2 MB'),
);
</script>

<template>
  <input type="file" accept="image/*" @change="form.values.avatar = ($event.target as HTMLInputElement).files?.[0] ?? null" />
</template>

См. также ​