Keep Inputs and State in Sync
In Module 4, you built a booking form with $state and manual event handlers. Every input needed an oninput handler to update state. That works, but Svelte offers something cleaner: the bind directive creates two-way binding — changes flow both ways automatically.
What You’ll Learn
- Understand one-way vs two-way binding
- Use
bind:valuefor text inputs - See how binding simplifies form code
The Manual Approach
Here’s how you might handle an input without binding:
<script>
let name = $state('');
function handleInput(event) {
name = event.target.value;
}
</script>
<input type="text" value={name} oninput={handleInput} />
<p>Hello, {name}!</p> This creates one-way data flow:
- State → Input:
value={name}displays the current value - Input → State:
oninput={handleInput}updates state when user types
It works, but every input needs its own handler. With ten form fields, that’s ten handlers.
The Binding Approach
The bind:value directive handles both directions:
<script>
let name = $state('');
</script>
<input type="text" bind:value={name} />
<p>Hello, {name}!</p> That’s it. No handler needed. When the user types, name updates. When name changes programmatically, the input updates.
How It Works
bind:value is syntactic sugar. The compiler transforms it into:
<input
type="text"
value={name}
oninput={(e) => name = e.target.value}
/> But you don’t write that — Svelte handles it. The binding keeps state and DOM perfectly synchronized.
Binding to Different Input Types
bind:value works with any input that has a value:
Text input:
<input type="text" bind:value={customerName} /> Email input:
<input type="email" bind:value={email} /> Password input:
<input type="password" bind:value={password} /> Number input:
<input type="number" bind:value={quantity} /> Note: For type="number", the bound value is automatically coerced to a number (or null if empty).
Textarea:
<textarea bind:value={notes}></textarea> Select dropdown:
<select bind:value={selectedService}>
<option value="lawn-mowing">Lawn Mowing</option>
<option value="hedge-trimming">Hedge Trimming</option>
</select> Apply to BookIt
Let’s simplify the booking form from Module 4:
<!-- filename: src/lib/components/BookingForm.svelte -->
<script>
let customerName = $state('');
let customerEmail = $state('');
let notes = $state('');
</script>
<form>
<label>
Name
<input type="text" bind:value={customerName} />
</label>
<label>
Email
<input type="email" bind:value={customerEmail} />
</label>
<label>
Notes
<textarea bind:value={notes}></textarea>
</label>
</form>
<div class="preview">
<h3>Booking Preview</h3>
<p>Name: {customerName || 'Not entered'}</p>
<p>Email: {customerEmail || 'Not entered'}</p>
<p>Notes: {notes || 'None'}</p>
</div> Three inputs, zero handlers. The preview updates as you type.
Binding vs Event Handlers
When should you use each?
Use bind:value when:
- You want simple two-way sync
- The input directly represents state
- No transformation is needed
Use event handlers when:
- You need to transform input (e.g., uppercase)
- You want to validate on every keystroke
- You need to trigger side effects
<script>
let username = $state('');
function handleInput(event) {
// Force lowercase, no spaces
username = event.target.value.toLowerCase().replace(/\s/g, '');
}
</script>
<!-- Transform input as user types -->
<input type="text" value={username} oninput={handleInput} /> For most form fields, bind:value is the right choice.
Common Mistakes
Forgetting the Colon
<!-- ❌ This sets a static attribute, not a binding -->
<input type="text" value={name} />
<!-- ✅ This creates two-way binding -->
<input type="text" bind:value={name} /> Without bind:, changes in the input won’t update state.
Binding to Non-State Variables
<script>
// ❌ Regular variable won't trigger updates
let name = '';
// ✅ Use $state for reactive binding
let name = $state('');
</script>
<input bind:value={name} /> Bindings work with $state to trigger reactivity.
Summary
The bind:value directive creates two-way binding between inputs and state. It eliminates boilerplate event handlers and keeps your forms clean. Use it for text inputs, textareas, selects, and number inputs.
Key takeaways:
bind:valuesyncs input and state automatically- Works with text, email, number, textarea, and select
- Replaces manual
value+oninputpatterns
Next Steps
Text inputs are covered. Continue with Customer Name and Email Fields to build out the booking form’s contact information section.