Skip to content

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.

vue
<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 / eventDescription
formThe form from useForm. Required
@submitCalled 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
@invalidCalled 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.

PropDescription
nameField 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
valueOption value for radio and checkbox groups
multipleSeveral files or <select> options
formThe form explicitly — if there is no <TvForm> up the tree
FieldValue 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:

vue
<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.

PropDescription
nameField path
asTag, span by default
formThe form explicitly — if there is no <TvForm> up the tree

Custom markup — via the { message, id } slot.

vue
<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…

vue
<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 ​