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:value for 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:

  1. State → Input: value={name} displays the current value
  2. 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:value syncs input and state automatically
  • Works with text, email, number, textarea, and select
  • Replaces manual value + oninput patterns

Next Steps

Text inputs are covered. Continue with Customer Name and Email Fields to build out the booking form’s contact information section.