Skip to main content

FormField

FormField

Complete form field component with label, input, and validation

Tags: component form validation

View Demo

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 for attribute
  • aria-invalid set when there's an error
  • aria-describedby links 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"
/>
  • Input - Standalone input component
  • Label - Standalone label component
  • Textarea - Standalone textarea component
On this page