Skip to content
Componentsbeta

Field & Label

Field wraps one control with its label, hint and error and wires them together: for/id, aria-describedby, aria-invalid. Use it for every form control.

Example

Type something invalid and leave the field.

We send your ticket here.

vue
<Field label="Email" hint="We send your ticket here." :error="emailError">
  <Input v-model="email" type="email" autocomplete="email" />
</Field>

Anatomy

So the teacher can tag you.

Use your handle, e.g. @ana.dances
  1. Label — text-sm bold, above. "(optional)" when not required.
  2. Control — Input, Select, Textarea or Checkbox.
  3. Hint — text-xs muted. Stays visible while the error shows.
  4. Error — FieldError, text-xs bold red, at ${id}-error.

With useFormValidation

Pass the same id to Field and to fieldAttrs. The control merges both aria-describedby values without duplicates.

vue
<script setup lang="ts">
const { errors, validate, fieldAttrs } = useFormValidation(BookingSchema, form)
</script>

<Field id="book-email" label="Email" :error="errors.email">
  <!-- fieldAttrs registers the id so a failed submit focuses this field -->
  <Input v-model="form.email" type="email" v-bind="fieldAttrs('email', 'book-email-error')" />
</Field>

Custom controls

Anything else can read the wiring from the slot.

Used on your festival page.

vue
<Field label="Colour" v-slot="{ id, describedBy, invalid }">
  <input :id="id" type="color" :aria-describedby="describedBy" :aria-invalid="invalid || undefined">
</Field>

Label on its own

Field uses Label internally. Use Label directly only for layouts Field can't express (e.g. a label beside a checkbox without the slot).

vue
<Label for="promo">Promo code</Label>
<Input id="promo" />

Props

Field

PropTypeDefaultDescription
labelstring—Visible label above the control.
idstringautoControl id. Error id = `${id}-error`, hint id = `${id}-hint`. Pass one when using useFormValidation.
hintstring—Help text below the control, always visible, linked via aria-describedby.
errorstring—Error message. Truthy = the control gets aria-invalid and the red border.
optionalbooleanfalseAdds a quiet "(optional)" to the label.
requiredbooleanfalseSets aria-required on the control. No visual mark — we mark optional fields instead.
disabledbooleanfalseDisables the control inside.
hideLabelbooleanfalseKeeps the label for screen readers only. Rare — e.g. a search field next to a "Search" button.
slot props{ id, describedBy, invalid }—For custom controls that are not Input/Select/Textarea/Checkbox.

Label

PropTypeDefaultDescription
forstring—id of the control.
optionalbooleanfalseAppends "(optional)".

Do & don't

With country code, e.g. +49…

Add the country code.

Do — Keep the hint visible when an error appears — it often explains how to fix it.

Something went wrong

Don't — Put the error in a toast or at the top only. It has to sit next to the field it is about.

Accessibility

  • The label is a real label for — clicking it focuses the control.
  • aria-describedby lists the error first, then the hint, so the problem is read out before the help.
  • The error is plain text next to the field; the red border is a second signal, never the only one.
  • useFormValidation focuses the first invalid field on submit via the error id.