Complete form field component with label, input, and validation
Tags: component form validation
FormField combines label, input, error messages, and hints into a single accessible component.
Import
<script>
import { FormField } from '@classic-homes/theme-svelte';
</script>Basic Usage
<script>
let email = $state('');
</script>
<FormField label="Email" id="email" type="email" bind:value={email} placeholder="your@email.com" />Props
| Prop | Type | Default | Description |
|---|---|---|---|
label |
string |
Required | Label text |
labelAction |
Snippet |
- | Content beside the label |
id |
string |
Required | Input ID for accessibility |
type |
'text' | 'email' | 'password' | 'tel' | 'url' | 'number' | 'date' | 'textarea' |
'text' |
Input type |
value |
string |
'' |
Field value (bindable) |
error |
string |
- | Error message to display |
hint |
string |
- | Help text shown below input |
disabled |
boolean |
false |
Disables the field |
required |
boolean |
false |
Marks as required |
placeholder |
string |
- | Placeholder text |
autocomplete |
AutoFill |
- | Browser autocomplete hint |
name |
string |
- | Name attribute for forms |
rows |
number |
3 |
Rows for textarea type |
readonly |
boolean |
false |
Makes field read-only |
class |
string |
- | Additional CSS classes |
onValueChange |
(value: string) => void |
- | Callback when value changes |
Input Types
Text (Default)
<FormField label="Name" id="name" bind:value={name} />Email
<FormField label="Email" id="email" type="email" autocomplete="email" bind:value={email} />Password
Renders a PasswordInput, so the user can show or hide what
they typed.
<FormField
label="Password"
id="password"
type="password"
autocomplete="new-password"
bind:value={password}
/>Label Action
Put a link or button beside the label with labelAction, instead of pairing your own Label
with an empty label prop:
<FormField
label="Password"
id="password"
type="password"
autocomplete="current-password"
bind:value={password}
>
{#snippet labelAction()}
<a href="/forgot-password" class="text-sm text-primary hover:underline">Forgot password?</a>
{/snippet}
</FormField>Tel (Phone)
<FormField label="Phone" id="phone" type="tel" autocomplete="tel" bind:value={phone} />Number
<FormField label="Age" id="age" type="number" bind:value={age} />Date
<FormField label="Birth Date" id="birthdate" type="date" bind:value={birthdate} />URL
<FormField label="Website" id="website" type="url" placeholder="https://" bind:value={website} />Textarea
<FormField label="Description" id="description" type="textarea" rows={5} bind:value={description} />Validation
Error State
Display validation errors:
<FormField label="Email" id="email" type="email" bind:value={email} error={emailError} />The error message:
- Appears below the input
- Has
role="alert"for screen readers - Changes input border to destructive color
- Takes precedence over hint text
With Hint
Provide helpful guidance:
<FormField
label="Password"
id="password"
type="password"
bind:value={password}
hint="Must be at least 8 characters"
/>
Error vs Hint
When both error and hint are provided, only the error is displayed. The error always takes precedence.
Required Fields
Mark fields as required:
<FormField label="Email" id="email" type="email" required bind:value={email} />Required fields display:
- Visual asterisk (*) after the label
- Screen-reader-only "(required)" text
- Browser's native required validation
States
Disabled
<FormField label="Username" id="username" disabled value="johndoe" />Read-only
<FormField label="ID" id="user-id" readonly value="USR-12345" />Accessibility
FormField provides comprehensive accessibility:
- Label properly associated via
forattribute aria-invalidset when there's an erroraria-describedbylinks to error or hint- Error messages announced as alerts
- Required indicator with screen reader text
Examples
Login Form
<script>
let email = $state('');
let password = $state('');
let errors = $state({ email: '', password: '' });
function validate() {
errors = { email: '', password: '' };
if (!email) errors.email = 'Email is required';
else if (!email.includes('@')) errors.email = 'Invalid email address';
if (!password) errors.password = 'Password is required';
else if (password.length < 8) errors.password = 'Password must be at least 8 characters';
return !errors.email && !errors.password;
}
</script>
<form onsubmit|preventDefault={() => validate() && login()}>
<div class="space-y-4">
<FormField
label="Email"
id="email"
type="email"
required
autocomplete="email"
bind:value={email}
error={errors.email}
/>
<FormField
label="Password"
id="password"
type="password"
required
autocomplete="current-password"
bind:value={password}
error={errors.password}
/>
<Button type="submit" class="w-full">Sign In</Button>
</div>
</form>Contact Form
<form class="space-y-4">
<FormField label="Full Name" id="name" required autocomplete="name" bind:value={name} />
<FormField
label="Email"
id="email"
type="email"
required
autocomplete="email"
bind:value={email}
/>
<FormField
label="Phone"
id="phone"
type="tel"
autocomplete="tel"
bind:value={phone}
hint="Optional"
/>
<FormField
label="Message"
id="message"
type="textarea"
rows={5}
required
bind:value={message}
hint="Please describe your inquiry in detail"
/>
<Button type="submit">Send Message</Button>
</form>Controlled Field
<script>
let value = $state('');
function handleChange(newValue: string) {
// Normalize phone number
value = newValue.replace(/\D/g, '').slice(0, 10);
}
</script>
<FormField
label="Phone"
id="phone"
type="tel"
{value}
onValueChange={handleChange}
hint="10 digits only"
/>