The $bindable rune allows parent components to bind to child props, enabling two-way data flow.

Basic $bindable

svelte
<!-- Toggle.svelte -->
<script>
  let { on = $bindable(false) } = $props();
</script>

<button onclick={() => on = !on}>
  {on ? 'ON' : 'OFF'}
</button>

Parent can bind:

svelte
<script>
  let lightOn = $state(false);
</script>

<Toggle bind:on={lightOn} />
<p>Light is {lightOn ? 'on' : 'off'}</p>

Multiple Bindable Props

svelte
<!-- Slider.svelte -->
<script>
  let {
    value = $bindable(50),
    min = $bindable(0),
    max = $bindable(100)
  } = $props();
</script>

<input 
  type="range" 
  bind:value 
  {min} 
  {max}
/>
svelte
<Slider bind:value={volume} bind:max={maxVolume} />

Bindable with Fallback

The value passed to $bindable() is the fallback when no binding is provided:

svelte
<script>
  let { value = $bindable('default text') } = $props();
</script>

<!-- If parent doesn't bind, value starts as 'default text' -->

Real-World Example: Custom Input

svelte
<!-- EmailInput.svelte -->
<script>
  let { 
    value = $bindable(''),
    valid = $bindable(false),
    ...rest 
  } = $props();
  
  // Validate email format
  $effect(() => {
    valid = /^[^@]+@[^@]+\.[^@]+$/.test(value);
  });
</script>

<div class="email-input" class:invalid={value && !valid}>
  <input 
    type="email" 
    bind:value
    {...rest}
  />
  {#if value && !valid}
    <span class="error">Invalid email</span>
  {/if}
</div>

Parent:

svelte
<script>
  let email = $state('');
  let emailValid = $state(false);
</script>

<EmailInput bind:value={email} bind:valid={emailValid} />

<button disabled={!emailValid}>
  Submit
</button>

When to Use $bindable vs Callback Props

Use CaseApproach
Form inputs (text, select)$bindable
Toggle states (open/close)$bindable
Events (click, submit)Callback props
One-time actions (delete, save)Callback props
Parent needs to know about changesEither works
svelte
<!-- Bindable: parent can sync state -->
<Modal bind:open={showModal} />

<!-- Callback: parent handles the action -->
<Modal onclose={() => showModal = false} />
✓ Completed