Components
Components do the same as the composables but without code in <script>: create the form with useForm, the rest is in the template. Fields find the form themselves — through <TvForm> up the tree.
<script setup lang="ts">
import { useForm, TvForm, TvField, TvError } from 'tinyval-vue';
const form = useForm(Signup, { initial: { name: '', email: '', plan: 'free', terms: false } });
const save = (data) => api.post('/signup', data); // 422 from Laravel is shown at the fields automatically
</script>
<template>
<TvForm :form="form" @submit="save">
<TvField name="name" placeholder="Name" />
<TvError name="name" />
<TvField name="email" type="email" />
<TvError name="email" />
<TvField name="plan" type="radio" value="free" /> Free
<TvField name="plan" type="radio" value="pro" /> Pro
<TvField name="terms" type="checkbox" /> I accept the terms
<button :disabled="form.submitting">Sign up</button>
</TvForm>
</template>TvForm
Renders <form novalidate>, submits through handleSubmit and provides the form to nested fields.
| Prop / event | Description |
|---|---|
form | The form from useForm. Required |
@submit | Called with validated data. If it returns a Promise, form.submitting is true until it settles; a rejection with a 422 response is spread over the fields |
@invalid | Called with the errors when validation fails |
The default slot receives { form }.
TvField
A field bound to the form by name. Other attributes (placeholder, class, autocomplete…) go to the element.
| Prop | Description |
|---|---|
name | Field path: email, address.city, items.0.title |
as | 'input' (default), 'textarea', 'select' or a UI library component with v-model |
type | <input> type: checkbox, radio, file and any other |
value | Option value for radio and checkbox groups |
multiple | Several files or <select> options |
form | The form explicitly — if there is no <TvForm> up the tree |
| Field | Value in the form |
|---|---|
<TvField name="bio" as="textarea" /> | a string |
<TvField name="terms" type="checkbox" /> | true / false |
<TvField name="tags" type="checkbox" value="js" /> | an array of checked values |
<TvField name="plan" type="radio" value="pro" /> | the value of the selected button |
<TvField name="city" as="select"> + <option> | the selected option value |
<TvField name="langs" as="select" multiple> | an array of values |
<TvField name="avatar" type="file" /> | a File or null; with multiple — an array |
<TvField name="email" :as="ElInput" /> | the component's v-model |
You can render fully custom markup with the slot — it receives field (for v-bind), value, setValue, error, errorId, touched, dirty:
<TvField name="email" v-slot="{ field, error, errorId }">
<el-form-item :error="error">
<el-input v-bind="field" />
</el-form-item>
</TvField>TvError
The field's error text, if it should be shown. Renders <span role="alert"> with the id the field's aria-describedby points to.
| Prop | Description |
|---|---|
name | Field path |
as | Tag, span by default |
form | The form explicitly — if there is no <TvForm> up the tree |
Custom markup — via the { message, id } slot.
<TvError name="email" v-slot="{ message, id }">
<p :id="id" class="text-red-600">⚠ {{ message }}</p>
</TvError>TvFieldArray
A list of fields — the slot receives everything useFieldArray returns: fields, append, remove, move…
<TvFieldArray name="items" v-slot="{ fields, append, remove }">
<div v-for="f in fields" :key="f.key">
<TvField :name="`${f.path}.title`" />
<TvError :name="`${f.path}.title`" />
<button type="button" @click="remove(f.index)">×</button>
</div>
<button type="button" @click="append({ title: '' })">+</button>
</TvFieldArray>See also
- useField — the same for your own field components
- UI libraries