Choose Your Service
The booking form needs service selection. When customers can only pick one service per booking, radio buttons work perfectly. Svelte’s bind:group connects multiple inputs to a single value.
What You’ll Learn
- Use
bind:groupfor radio buttons - Create service selection UI
- Handle checkbox groups for multiple selections
- Display selected service details
Radio Button Basics
Radio buttons let users select one option from a group. All radios in a group share the same name:
<script>
let selectedService = $state('');
</script>
<fieldset>
<legend>Select a Service</legend>
<label>
<input type="radio" name="service" value="lawn-mowing" bind:group={selectedService} />
Lawn Mowing
</label>
<label>
<input type="radio" name="service" value="hedge-trimming" bind:group={selectedService} />
Hedge Trimming
</label>
<label>
<input type="radio" name="service" value="consultation" bind:group={selectedService} />
Garden Consultation
</label>
</fieldset>
<p>Selected: {selectedService || 'None'}</p> bind:group connects all three inputs to selectedService. Clicking any radio updates the bound value to that input’s value.
Generate from Data
With real services, generate the options:
<script>
let selectedService = $state('');
const services = [
{ id: '1', slug: 'lawn-mowing', name: 'Lawn Mowing', price: 50 },
{ id: '2', slug: 'hedge-trimming', name: 'Hedge Trimming', price: 75 },
{ id: '3', slug: 'garden-consultation', name: 'Garden Consultation', price: 100 },
{ id: '4', slug: 'tree-pruning', name: 'Tree Pruning', price: 150 }
];
</script>
<fieldset>
<legend>Select a Service</legend>
{#each services as service}
<label class="service-option">
<input
type="radio"
name="service"
value={service.slug}
bind:group={selectedService}
/>
<span class="service-name">{service.name}</span>
<span class="service-price">${service.price}</span>
</label>
{/each}
</fieldset> Rich Service Cards
Make the selection more visual:
<script>
let selectedService = $state('');
const services = [
{
id: '1',
slug: 'lawn-mowing',
name: 'Lawn Mowing',
description: 'Professional lawn mowing service.',
price: 50,
duration: 60
},
{
id: '2',
slug: 'hedge-trimming',
name: 'Hedge Trimming',
description: 'Expert hedge and shrub trimming.',
price: 75,
duration: 90
},
{
id: '3',
slug: 'garden-consultation',
name: 'Garden Consultation',
description: 'One-on-one garden planning advice.',
price: 100,
duration: 60
}
];
let selectedDetails = $derived(
services.find(s => s.slug === selectedService)
);
</script>
<fieldset class="service-selection">
<legend>Select a Service</legend>
<div class="service-grid">
{#each services as service}
<label class="service-card" class:selected={selectedService === service.slug}>
<input
type="radio"
name="service"
value={service.slug}
bind:group={selectedService}
/>
<div class="card-content">
<h3>{service.name}</h3>
<p>{service.description}</p>
<div class="card-footer">
<span class="price">${service.price}</span>
<span class="duration">{service.duration} min</span>
</div>
</div>
</label>
{/each}
</div>
</fieldset>
{#if selectedDetails}
<div class="selection-summary">
<h3>You selected: {selectedDetails.name}</h3>
<p>Price: ${selectedDetails.price} · Duration: {selectedDetails.duration} minutes</p>
</div>
{/if}
<style>
.service-grid {
display: grid;
gap: 1rem;
}
.service-card {
display: block;
padding: 1rem;
border: 2px solid #ddd;
border-radius: 8px;
cursor: pointer;
transition: border-color 0.2s;
}
.service-card:hover {
border-color: #007bff;
}
.service-card.selected {
border-color: #007bff;
background: #f0f7ff;
}
.service-card input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.card-content h3 {
margin: 0 0 0.5rem 0;
}
.card-content p {
margin: 0 0 1rem 0;
color: #666;
}
.card-footer {
display: flex;
justify-content: space-between;
}
.price {
font-weight: bold;
color: #007bff;
}
.duration {
color: #666;
}
</style> The radio input is visually hidden — the entire card becomes clickable.
Checkbox Groups
What if customers can select multiple add-ons? Use checkboxes with bind:group:
<script>
let selectedAddOns = $state([]);
const addOns = [
{ id: 'cleanup', name: 'Garden Cleanup', price: 25 },
{ id: 'fertilizer', name: 'Lawn Fertilizer', price: 15 },
{ id: 'weed-control', name: 'Weed Control', price: 20 }
];
let addOnTotal = $derived(
addOns
.filter(addon => selectedAddOns.includes(addon.id))
.reduce((sum, addon) => sum + addon.price, 0)
);
</script>
<fieldset>
<legend>Add-Ons (Optional)</legend>
{#each addOns as addon}
<label class="addon-option">
<input
type="checkbox"
value={addon.id}
bind:group={selectedAddOns}
/>
<span>{addon.name}</span>
<span class="addon-price">+${addon.price}</span>
</label>
{/each}
</fieldset>
{#if selectedAddOns.length > 0}
<p>Add-ons total: ${addOnTotal}</p>
{/if} With checkboxes, bind:group uses an array. Selected values are added; deselected values are removed.
Bind:checked for Single Checkboxes
For a standalone checkbox (not a group), use bind:checked:
<script>
let agreeToTerms = $state(false);
let wantsNewsletter = $state(false);
</script>
<label>
<input type="checkbox" bind:checked={agreeToTerms} required />
I agree to the terms and conditions
</label>
<label>
<input type="checkbox" bind:checked={wantsNewsletter} />
Send me promotional emails
</label>
<button disabled={!agreeToTerms}>Book Now</button> bind:checked binds to a boolean — true when checked, false when not.
Complete Service Selection
Integrate into the booking form:
<!-- filename: src/lib/components/BookingForm.svelte (partial) -->
<script>
// Previous state...
let customerName = $state('');
let customerEmail = $state('');
let bookingDate = $state('');
let selectedTime = $state('');
// Service selection
let selectedService = $state('');
let selectedAddOns = $state([]);
let agreeToTerms = $state(false);
const services = [
{ slug: 'lawn-mowing', name: 'Lawn Mowing', price: 50, duration: 60 },
{ slug: 'hedge-trimming', name: 'Hedge Trimming', price: 75, duration: 90 },
{ slug: 'garden-consultation', name: 'Garden Consultation', price: 100, duration: 60 }
];
const addOns = [
{ id: 'cleanup', name: 'Garden Cleanup', price: 25 },
{ id: 'fertilizer', name: 'Lawn Fertilizer', price: 15 }
];
let selectedServiceDetails = $derived(
services.find(s => s.slug === selectedService)
);
let totalPrice = $derived(() => {
if (!selectedServiceDetails) return 0;
const addOnTotal = addOns
.filter(a => selectedAddOns.includes(a.id))
.reduce((sum, a) => sum + a.price, 0);
return selectedServiceDetails.price + addOnTotal;
});
</script>
<form>
<!-- Contact and date fields... -->
<fieldset>
<legend>Select a Service</legend>
{#each services as service}
<label class="service-option" class:selected={selectedService === service.slug}>
<input
type="radio"
name="service"
value={service.slug}
bind:group={selectedService}
required
/>
<span class="name">{service.name}</span>
<span class="details">${service.price} · {service.duration} min</span>
</label>
{/each}
</fieldset>
<fieldset>
<legend>Add-Ons (Optional)</legend>
{#each addOns as addon}
<label class="addon-option">
<input
type="checkbox"
value={addon.id}
bind:group={selectedAddOns}
/>
{addon.name} (+${addon.price})
</label>
{/each}
</fieldset>
<div class="total">
<strong>Total: ${totalPrice()}</strong>
</div>
<label class="terms">
<input type="checkbox" bind:checked={agreeToTerms} required />
I agree to the terms and conditions
</label>
<button type="submit" disabled={!agreeToTerms}>
Book Now
</button>
</form> Common Mistakes
Forgetting name Attribute
<!-- ❌ Without name, radios don't form a group natively -->
<input type="radio" value="a" bind:group={selected} />
<input type="radio" value="b" bind:group={selected} />
<!-- ✅ Include name for proper form behavior -->
<input type="radio" name="choice" value="a" bind:group={selected} />
<input type="radio" name="choice" value="b" bind:group={selected} /> Using Wrong Binding
<!-- ❌ bind:value doesn't work for radio groups -->
<input type="radio" bind:value={selected} />
<!-- ✅ Use bind:group for radios -->
<input type="radio" bind:group={selected} />
<!-- ❌ bind:group for single checkbox -->
<input type="checkbox" bind:group={agree} />
<!-- ✅ Use bind:checked for single checkbox -->
<input type="checkbox" bind:checked={agree} /> Summary
bind:group connects radio buttons and checkboxes to shared state. Radios bind to a single value; checkboxes bind to an array. Use bind:checked for standalone checkboxes.
Key takeaways:
bind:groupfor radio buttons → single valuebind:groupfor checkboxes → array of valuesbind:checkedfor single checkbox → boolean- Include
nameattribute for form compatibility
Next Steps
The form captures service selection. Continue with Add Optional Notes Textarea to let customers provide additional instructions.