Collect Customer Information

Every booking needs contact details. The customer’s name and email are essential — we need to know who’s booking and how to reach them. Let’s build these fields properly with labels, placeholders, and basic browser validation.


What You’ll Learn

  • Create labeled form fields
  • Add placeholder text for guidance
  • Use HTML5 validation attributes
  • Structure accessible form markup

Start with the Name Field

A proper form field needs more than just an input. It needs a label:

<!-- filename: src/lib/components/BookingForm.svelte -->
<script>
  let customerName = $state('');
</script>

<form>
  <div class="field">
    <label for="customer-name">Your Name</label>
    <input 
      type="text" 
      id="customer-name"
      bind:value={customerName}
      placeholder="Jane Smith"
      required
    />
  </div>
</form>

Key elements:

  • <label for="..."> connects to <input id="...">
  • placeholder shows example text when empty
  • required prevents submission without a value

Add the Email Field

Email inputs get special treatment in browsers:

<script>
  let customerName = $state('');
  let customerEmail = $state('');
</script>

<form>
  <div class="field">
    <label for="customer-name">Your Name</label>
    <input 
      type="text" 
      id="customer-name"
      bind:value={customerName}
      placeholder="Jane Smith"
      required
    />
  </div>
  
  <div class="field">
    <label for="customer-email">Email Address</label>
    <input 
      type="email" 
      id="customer-email"
      bind:value={customerEmail}
      placeholder="jane@example.com"
      required
    />
  </div>
</form>

Using type="email" provides:

  • Mobile keyboards optimized for email entry
  • Built-in format validation (must contain @)
  • Browser autocomplete for email addresses

Add a Phone Field (Optional)

Some bookings benefit from a phone number:

<script>
  let customerName = $state('');
  let customerEmail = $state('');
  let customerPhone = $state('');
</script>

<form>
  <!-- Name and Email fields... -->
  
  <div class="field">
    <label for="customer-phone">Phone Number (optional)</label>
    <input 
      type="tel" 
      id="customer-phone"
      bind:value={customerPhone}
      placeholder="(555) 123-4567"
    />
  </div>
</form>

Notice: no required attribute. The “(optional)” text tells users it’s not mandatory.


The Complete Contact Section

Here’s the full contact information section:

<!-- filename: src/lib/components/BookingForm.svelte -->
<script>
  let customerName = $state('');
  let customerEmail = $state('');
  let customerPhone = $state('');
</script>

<form>
  <fieldset>
    <legend>Contact Information</legend>
    
    <div class="field">
      <label for="customer-name">Your Name</label>
      <input 
        type="text" 
        id="customer-name"
        name="customerName"
        bind:value={customerName}
        placeholder="Jane Smith"
        required
        autocomplete="name"
      />
    </div>
    
    <div class="field">
      <label for="customer-email">Email Address</label>
      <input 
        type="email" 
        id="customer-email"
        name="customerEmail"
        bind:value={customerEmail}
        placeholder="jane@example.com"
        required
        autocomplete="email"
      />
    </div>
    
    <div class="field">
      <label for="customer-phone">Phone Number (optional)</label>
      <input 
        type="tel" 
        id="customer-phone"
        name="customerPhone"
        bind:value={customerPhone}
        placeholder="(555) 123-4567"
        autocomplete="tel"
      />
    </div>
  </fieldset>
</form>

<style>
  fieldset {
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 1.5rem;
    margin-bottom: 1.5rem;
  }
  
  legend {
    font-weight: 600;
    padding: 0 0.5rem;
  }
  
  .field {
    margin-bottom: 1rem;
  }
  
  .field:last-child {
    margin-bottom: 0;
  }
  
  label {
    display: block;
    margin-bottom: 0.25rem;
    font-weight: 500;
  }
  
  input {
    width: 100%;
    padding: 0.5rem;
    border: 1px solid #ccc;
    border-radius: 4px;
    font-size: 1rem;
  }
  
  input:focus {
    outline: none;
    border-color: #007bff;
    box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
  }
</style>

New additions:

  • <fieldset> and <legend> group related fields
  • name attributes for form submission
  • autocomplete hints help browsers fill forms faster

Preview the Entered Data

Show users what they’ve entered:

<script>
  let customerName = $state('');
  let customerEmail = $state('');
  let customerPhone = $state('');
  
  let hasContactInfo = $derived(customerName || customerEmail);
</script>

<!-- Form fields... -->

{#if hasContactInfo}
  <div class="preview">
    <h3>Contact Details</h3>
    {#if customerName}
      <p><strong>Name:</strong> {customerName}</p>
    {/if}
    {#if customerEmail}
      <p><strong>Email:</strong> {customerEmail}</p>
    {/if}
    {#if customerPhone}
      <p><strong>Phone:</strong> {customerPhone}</p>
    {/if}
  </div>
{/if}

The preview only appears once the user starts entering data.


Accessibility Matters

Our form is accessible because:

  1. Labels are connected to inputs via for/id — clicking the label focuses the input
  2. Required fields are marked — screen readers announce “required”
  3. Fieldset groups related content — provides context for screen reader users
  4. Input types are semanticemail and tel convey meaning

Common Mistakes

Missing Label Connection

<!-- ❌ Label not connected to input -->
<label>Email</label>
<input type="email" bind:value={email} />

<!-- ✅ Connected via for/id -->
<label for="email">Email</label>
<input type="email" id="email" bind:value={email} />

<!-- ✅ Or wrap input in label -->
<label>
  Email
  <input type="email" bind:value={email} />
</label>

Placeholder as Label

<!-- ❌ Placeholder disappears when typing -->
<input type="text" placeholder="Your Name" />

<!-- ✅ Always have a visible label -->
<label for="name">Your Name</label>
<input type="text" id="name" placeholder="Jane Smith" />

Placeholders are hints, not labels.


Summary

Customer contact fields need labels, proper input types, and accessibility attributes. Use bind:value to keep state synchronized, and group related fields with <fieldset>.

Key takeaways:

  • Connect labels to inputs with for/id
  • Use semantic input types (email, tel)
  • Add required for mandatory fields
  • Include autocomplete for better UX

Next Steps

Contact details are covered. Continue with Date Picker Input to let customers choose when they want their service.