FormField
Wrap any form control with labels, descriptions, hints, error messages, and help text. Provides context to child components for automatic error handling and sizing.
for attribute cannot reach. The description stays announced next to an error, required sets aria-required on text controls, a control with its own id keeps its label, and aria-* attributes or event handlers you pass to a control are merged instead of overwritten.Playground
Experiment with different props in real-time.
Your work email
We'll send a confirmation
Basic Usage
Wrap a form control with a label.
<script lang="ts">
import { FormField, Input } from 'sv5ui';
</script>
<FormField label="Email">
<Input placeholder="you@example.com" />
</FormField>Description
Add context below the label.
Must be at least 8 characters
<FormField label="Password" description="Must be at least 8 characters">
<Input type="password" placeholder="Enter password" />
</FormField>Hint
Display a hint next to the label (right side).
<FormField label="Username" hint="Required">
<Input placeholder="johndoe" />
</FormField>
<FormField label="Bio" hint="Optional">
<Textarea placeholder="Tell us about yourself..." />
</FormField>Help Text
Display help text below the control.
Include the full URL with https://
<FormField label="Website" help="Include the full URL with https://">
<Input placeholder="https://example.com" leadingIcon="lucide:globe" />
</FormField>Error
Error message replaces help text and auto-highlights the child control.
Please enter a valid email address
Password is too short
<!-- String error message -->
<FormField label="Email" error="Please enter a valid email address">
<Input placeholder="you@example.com" />
</FormField>
<!-- Boolean error (no message) -->
<FormField label="Name" error={true}>
<Input placeholder="Your name" />
</FormField>
<!-- Error replaces help text -->
<FormField label="Password" help="Must be 8+ chars" error="Password is too short">
<Input type="password" />
</FormField>Required
Adds a red asterisk to the label and sets aria-required on the text controls inside: Input, InputNumber, InputTags and Textarea. Other controls take their own required prop.
<FormField label="Full Name" required>
<Input placeholder="John Doe" />
</FormField>
<FormField label="Email" required hint="We'll never share your email">
<Input type="email" placeholder="you@example.com" />
</FormField>Sizes
Controls label and text size. Also propagates to child components.
<FormField label="Extra Small" size="xs">
<Input placeholder="XS input" />
</FormField>
<FormField label="Small" size="sm">
<Input placeholder="SM input" />
</FormField>
<FormField label="Medium" size="md">
<Input placeholder="MD input" />
</FormField>
<FormField label="Large" size="lg">
<Input placeholder="LG input" />
</FormField>Horizontal
Side-by-side label and control layout.
<FormField label="Name" orientation="horizontal">
<Input placeholder="John Doe" />
</FormField>
<FormField label="Email" orientation="horizontal" required>
<Input type="email" placeholder="you@example.com" />
</FormField>
<FormField label="Role" orientation="horizontal">
<Select items={roles} placeholder="Select role" />
</FormField>With Input
Find anything in your workspace
<FormField label="Search" description="Find anything in your workspace">
<Input icon="lucide:search" placeholder="Search..." />
</FormField>
<FormField label="Amount" hint="USD">
<Input type="number" leadingIcon="lucide:dollar-sign" placeholder="0.00" />
</FormField>With Textarea
Describe your issue in detail
Markdown supported
<FormField label="Message" description="Describe your issue in detail" help="Markdown supported">
<Textarea placeholder="Type your message..." autoresize />
</FormField>With Select & SelectMenu
Choose your preferred framework
<FormField label="Country" required>
<Select items={countries} placeholder="Select a country" />
</FormField>
<FormField label="Framework" description="Choose your preferred framework">
<SelectMenu items={frameworks} placeholder="Search frameworks..." />
</FormField>With CheckboxGroup
<FormField label="Preferences">
<CheckboxGroup items={[
{ value: 'email', label: 'Email notifications' },
{ value: 'sms', label: 'SMS notifications' },
{ value: 'push', label: 'Push notifications' }
]} />
</FormField>With Switch
Toggle dark theme
<FormField label="Dark Mode" description="Toggle dark theme">
<Switch />
</FormField>Complete Form
A real-world registration form combining multiple form components.
<form class="space-y-6">
<FormField label="Full Name" required>
<Input placeholder="John Doe" />
</FormField>
<FormField label="Email" required hint="We'll never share it">
<Input type="email" placeholder="you@example.com" leadingIcon="lucide:mail" />
</FormField>
<FormField label="Password" required description="Must be at least 8 characters">
<Input type="password" placeholder="Enter password" />
</FormField>
<FormField label="Role">
<Select items={roles} placeholder="Select role" />
</FormField>
<FormField label="Bio" hint="Optional">
<Textarea placeholder="Tell us about yourself..." autoresize />
</FormField>
<FormField label="Notifications">
<Switch label="Receive email updates" />
</FormField>
<Button type="submit" label="Create Account" block />
</form>Accessibility
A field wires its control up for assistive technology without any props from you. The demo reads the attributes back from the input, so you can watch them change.
Shown on your profile
3 to 20 characters
0 / 20
- aria-describedby
- aria-invalid
- not set
- aria-required
- not set
<script lang="ts">
import { FormField, Input } from 'sv5ui';
let username = $state('');
</script>
<FormField
name="username"
label="Username"
description="Shown on your profile"
help="3 to 20 characters"
required
>
<!-- Your id is merged with the field's ids, yours first -->
<Input bind:value={username} aria-describedby="username-count" />
</FormField>
<p id="username-count">{username.length} / 20</p>- Name. The label names the control, through
forwhere a label can point at it and througharia-labelledbywhere it cannot. Anaria-labeloraria-labelledbyyou pass to the control wins. - Description.
aria-describedbylists the description and the help text, and only the ones that are rendered. When an error appears it takes the place of the help text, the description stays, and the control getsaria-invalid. - Your own attributes. An
aria-describedbyon the control is merged with the field's ids, yours first, and anaria-invalidyou set is kept. Event handlers you pass run alongside the field's own. - Your own id. Give the control an
idand the label follows it, as do the errors of a surrounding Form. - Slots. Content passed to
descriptionSlot,helpSlotanderrorSlotis announced too. Each is rendered inside a wrapper withdisplay: contentsthat carries the id, so layout is unchanged but a direct child selector now meets the wrapper first.
A control with no visible label needs a name from you. Inside a field the label provides it:
Applies to this screen only
<!-- On its own: name the control yourself. The attribute lands on the thumb -->
<Slider aria-label="Volume" value={40} />
<!-- In a field: the label names it, and the description is announced -->
<FormField label="Brightness" description="Applies to this screen only">
<Slider value={70} />
</FormField>Controls That Follow These Rules
How the field label reaches each control, and whether required on the field sets aria-required on it.
| Control | Named through | aria-required from the field |
|---|---|---|
Input | label for | Yes |
InputNumber | label for | Yes |
InputTags | label for | Yes |
Textarea | label for | Yes |
Select | label for | No |
Checkbox | label for | No |
Switch | label for | No |
SelectMenu | aria-labelledby | No |
Rating | aria-labelledby | No |
Slider | aria-labelledby on each thumb | No |
RadioGroup | aria-labelledby, unless it has a legend | No |
CheckboxGroup | aria-labelledby, unless it has a legend | No |
ColorPicker | aria-labelledby on the group | No |
InputPassword is built on Input and behaves the same. The other controls that read a field (Calendar, DatePicker, DateRangePicker, TimeField, PinInput, FileUpload, Editor and ImageCropper) have not moved to these rules yet, so do not rely on merged attributes there.
UI Slots
Use the ui prop to override classes on internal elements.
| Slot | Description |
|---|---|
root | Outermost wrapper |
wrapper | Contains label section and description |
labelWrapper | Flex container for label and hint |
label | Label text element |
description | Description text below label |
hint | Hint text next to label |
container | Contains form control + error/help |
error | Error message text |
help | Help text below control |
Snippets
| Snippet | Description |
|---|---|
children | Form control content (receives error state) |
labelSlot | Custom label rendering |
hintSlot | Custom hint rendering |
descriptionSlot | Custom description rendering. Wrapped in an element that carries the description id |
helpSlot | Custom help text rendering. Wrapped in an element that carries the help id |
errorSlot | Custom error rendering. Wrapped in an element that carries the error id |
Props
| Prop | Type | Default |
|---|---|---|
label | string | - |
description | string | - |
hint | string | - |
help | string | - |
error | string | boolean | - |
name | string | - |
eagerValidation | boolean | false |
validateOnInputDelay | number | - |
size | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' |
orientation | 'vertical' | 'horizontal' | 'vertical' |
required | boolean | false |
ref | HTMLElement | null | null |
class | string | - |
ui | Record<Slot, Class> | - |