Skip to main content

Forms

A comprehensive guide to form components, validation patterns, and best practices for building accessible forms

Forms

Build accessible, user-friendly forms with our comprehensive set of form components. Each component follows consistent patterns, uses typography tokens for visual hierarchy, and includes built-in accessibility features.

Available Components

Input Fields

  • TextInput - Text entry for various data types (text, email, password, etc.)
  • Textarea - Multi-line text input for longer content with auto-resize
  • Select - Dropdown selection from predefined options

Selection Controls

  • Checkbox - Multiple selection from a group of options
  • RadioGroup - Single selection from mutually exclusive options
  • Toggle - Binary on/off switches for immediate settings

Design Principles

1. Consistent Typography

All form components use the design system’s typography tokens to maintain visual hierarchy:

/* Labels */
--typography-caption-size
--typography-caption-color
--typography-caption-weight

/* Input text */
--typography-body-size
--typography-body-color
--typography-body-weight

/* Helper/Error text */
--typography-caption-size
--typography-muted-color /* helper */
--error /* error state */

2. Clear Visual States

Every form component supports these states:

  • Default - Normal interactive state
  • Focus - Clear focus ring for keyboard navigation
  • Disabled - Reduced opacity and no interaction
  • Error - Red border with error message
  • Required - Asterisk indicator on label

3. Accessibility First

All components include:

  • Proper ARIA attributes (aria-required, aria-invalid, aria-describedby)
  • Keyboard navigation support
  • Screen reader announcements
  • Proper label associations
  • Focus management

Form Patterns

Basic Form Structure

<script>
  import TextInput from '$lib/shared/components/forms/TextInput.svelte';
  import Checkbox from '$lib/shared/components/forms/Checkbox.svelte';
  import Button from '$lib/shared/components/primitives/Button.svelte';
  
  let formData = $state({
    name: '',
    email: '',
    agree: false
  });
  
  let errors = $state({});
  
  function handleSubmit(e) {
    e.preventDefault();
    // Validation logic
    if (!formData.name) {
      errors.name = 'Name is required';
      return;
    }
    // Submit logic
  }
</script>

<form on:submit={handleSubmit}>
  <TextInput
    id="name"
    label="Full Name"
    bind:value={formData.name}
    error={errors.name}
    required
  />
  
  <TextInput
    id="email"
    type="email"
    label="Email Address"
    bind:value={formData.email}
    error={errors.email}
    required
  />
  
  <Checkbox
    id="agree"
    label="I agree to the terms"
    bind:checked={formData.agree}
    error={errors.agree}
    required
  />
  
  <Button type="submit">Submit</Button>
</form>

Validation Strategies

1. On Submit Validation

Validate all fields when the form is submitted:

function validateForm(data) {
  const errors = {};
  
  if (!data.email) {
    errors.email = 'Email is required';
  } else if (!data.email.includes('@')) {
    errors.email = 'Invalid email format';
  }
  
  return errors;
}

2. On Blur Validation

Validate individual fields when they lose focus:

<TextInput
  id="email"
  type="email"
  label="Email"
  bind:value={email}
  error={emailError}
  onblur={() => {
    if (!email.includes('@')) {
      emailError = 'Invalid email';
    }
  }}
/>

3. Real-time Validation

Validate as the user types (use sparingly):

$effect(() => {
  if (password.length > 0 && password.length < 8) {
    passwordError = 'Password must be at least 8 characters';
  } else {
    passwordError = null;
  }
});

Form Layout

Vertical Stacking

Default pattern for most forms:

.form-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-width: 480px;
}

Grid Layout

For complex forms with multiple columns:

.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-6);
}

Grouped Sections

Use fieldsets to group related fields:

<fieldset>
  <legend>Contact Information</legend>
  <TextInput id="email" label="Email" />
  <TextInput id="phone" label="Phone" />
</fieldset>

Error Handling

Error Message Guidelines

  1. Be specific - “Enter a valid email address” not “Invalid input”
  2. Be helpful - Explain what’s needed to fix the error
  3. Be timely - Show errors at the right moment
  4. Be visible - Use color and icons for clarity

Error Display Pattern

{#if error}
  <div role="alert" aria-live="polite" class="field-error-message">
    <Icon src="/svg/error.svg" size={16} />
    <span>{error}</span>
  </div>
{/if}

Accessibility Checklist

Labels

  • ✓ Every input has a label
  • ✓ Labels are associated via for/id
  • ✓ Required fields marked with asterisk
  • ✓ Helper text linked via aria-describedby

Keyboard Navigation

  • ✓ All inputs reachable via Tab
  • ✓ Focus indicators visible
  • ✓ Enter submits forms
  • ✓ Escape cancels modals

Screen Readers

  • ✓ Error messages announced
  • ✓ Required fields identified
  • ✓ Field purposes clear
  • ✓ Form submission feedback

Visual Design

  • ✓ Color not sole indicator
  • ✓ Sufficient contrast ratios
  • ✓ Touch targets ≥44×44px
  • ✓ Clear visual hierarchy

Best Practices

1. Progressive Disclosure

Show only necessary fields initially:

  • Use conditional fields based on previous answers
  • Group advanced options in collapsible sections
  • Implement multi-step forms for complex processes

2. Smart Defaults

Reduce user effort:

  • Pre-fill known information
  • Use sensible default values
  • Remember user preferences
  • Auto-format inputs (phone, date, etc.)

3. Clear Actions

Make form intentions obvious:

  • Use descriptive button labels (“Create Account” not “Submit”)
  • Differentiate primary and secondary actions
  • Provide clear cancel/reset options
  • Show loading states during submission

4. Mobile Optimization

Ensure forms work on all devices:

  • Use appropriate input types (type="email", type="tel")
  • Ensure touch targets are large enough
  • Test with virtual keyboards
  • Consider landscape orientation

Form Examples

Login Form

Simple authentication with email and password

Registration Form

Multi-step user registration with validation

Settings Form

Preferences with toggles and checkboxes

Contact Form

Message submission with file upload

Search Form

Filters with various input types

Performance Tips

  1. Debounce validations - Don’t validate on every keystroke
  2. Lazy load components - Load complex inputs only when needed
  3. Optimize re-renders - Use $state and $derived efficiently
  4. Cache form data - Preserve user input on navigation
  5. Batch updates - Group multiple field changes