FormField
Label, description, hint, and error-message wrapper that wires id/aria-describedby/invalid into whatever form control it wraps.
Usage
Wrap any single form control - Input, Textarea, Select, Autocomplete, Checkbox,
RadioGroup, or Switch - and FormField generates a matching id, links the
label to it, and computes aria-describedby from whichever of hint/error
is currently showing. The wrapped control picks all of this up automatically;
you don't pass id or aria-describedby yourself.
<script setup lang="ts">
const email = ref('')
</script>
<template>
<SFormField label="Email" name="email" hint="We'll never share this.">
<SInput v-model="email" type="email" placeholder="[email protected]" />
</SFormField>
</template>
Description
description adds a second, muted line under the label, before the
control - for context the user should read before reaching it (unlike
hint, which sits below the control instead):
<SFormField label="Email" description="We'll use this to send your receipt.">
<SInput v-model="email" type="email" />
</SFormField>
Error state
Set error to a string to show a message in place of the hint and mark the
control invalid (a red ring/outline, aria-invalid="true"). Passing
error as true (no message) still marks the control invalid without
rendering any text.
<SFormField label="Email" error="Enter a valid email address">
<SInput v-model="email" type="email" />
</SFormField>
Required indicator
<SFormField label="Name" required>
<SInput v-model="name" />
</SFormField>
Sizes
size also scales FormField's own label/description/hint/error text
(sm / md / lg) - it's the same prop already forwarded to the
wrapped control's own size, so both stay visually in sync without
setting it twice:
<SFormField label="Email" size="sm">
<SInput v-model="email" size="sm" />
</SFormField>
<SFormField label="Email" size="lg">
<SInput v-model="email" size="lg" />
</SFormField>
Orientation
orientation="horizontal" places the label beside the control instead
of above it, with the control filling the remaining row width - useful
for settings-page-style label-left forms. hint/error still render
full-width below the row either way:
<SFormField label="Email" orientation="horizontal">
<SInput v-model="email" />
</SFormField>
Floating label
label-mode="floating" places the label over an empty control and moves it
above the border when the control is focused or has a value. It keeps the same
native label association, hint, and error behavior. Input and Textarea detect
their native value state; Select, Autocomplete, and DatePicker expose their
selection state to the field. For a custom control, pass :filled when it has
a value. Floating mode uses the vertical layout even if orientation is set
to horizontal. DatePicker's editable segmented field keeps the label raised
while empty so its day, month, and year placeholders remain visible and typeable.
We'll send your receipt here.
<SFormField label="Email" label-mode="floating" hint="We'll send your receipt here.">
<SInput v-model="email" type="email" />
</SFormField>
An SInputGroup containing one Input or Select plus an addon button also
works when the control comes first. For two editable controls, wrap each
input in its own SFormField so both have independent labels, names, and
validation state. This works with either InputGroup orientation:
Horizontal
Vertical
Works with any control
<SFormField label="Plan" name="plan">
<SSelect v-model="plan" :items="planItems" />
</SFormField>
<SFormField error="You must accept the terms">
<SCheckbox v-model="accepted" label="Accept terms" />
</SFormField>
A control's own id/name still win over FormField's if you set them
explicitly. invalid is OR'd with the field's error state instead, so a
control can opt into looking invalid on its own even without an error, but
can't be forced back to valid while its FormField has one.
Custom :ui
To see exactly what you'd be overriding - the current default classes for
every slot and variant - here's FormField's own theme file:
Props
| Prop | Type | Default |
|---|---|---|
label | string | - |
name | string | - |
description | string | - |
hint | string | - |
error | string | boolean | - |
required | boolean | false |
size | 'sm' | 'md' | 'lg' | - |
orientation | 'vertical' | 'horizontal' | vertical |
labelMode | 'static' | 'floating' | static |
filled | boolean | false |
ui | Partial<Record<'root' | 'body' | 'header' | 'label' | 'required' | 'description' | 'container' | 'hint' | 'error', string | object>> | - |
Slots
| Slot | Props | Description |
|---|---|---|
| default | { id, invalid, describedBy } | The wrapped control. Props are also available if you need to bind them manually. |
description | - | Custom content, overrides description |