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="...">placeholdershows example text when emptyrequiredprevents 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 fieldsnameattributes for form submissionautocompletehints 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:
- Labels are connected to inputs via
for/id— clicking the label focuses the input - Required fields are marked — screen readers announce “required”
- Fieldset groups related content — provides context for screen reader users
- Input types are semantic —
emailandtelconvey 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
requiredfor mandatory fields - Include
autocompletefor better UX
Next Steps
Contact details are covered. Continue with Date Picker Input to let customers choose when they want their service.