Sometimes you want a parent component to bind to a child's prop — like how you bind to an input's value. The $bindable rune makes this possible.
The Problem
By default, props flow one way (parent → child). What if you want changes in the child to flow back up?
Without $bindable
You'd need callback props:
svelte<!-- Child.svelte --> <script> let { value, onchange } = $props(); </script> <input value={value} oninput={(e) => onchange(e.target.value)} />
svelte<!-- Parent.svelte --> <script> let text = $state(''); </script> <Child value={text} onchange={(v) => text = v} />
This works, but it's verbose.
With $bindable
svelte<!-- FancyInput.svelte --> <script> let { value = $bindable('') } = $props(); </script> <input bind:value={value} class="fancy" /> <style> .fancy { border: 2px solid purple; padding: 8px; border-radius: 8px; } </style>
svelte<!-- Parent.svelte --> <script> import FancyInput from './FancyInput.svelte'; let username = $state(''); </script> <FancyInput bind:value={username} /> <p>Username: {username}</p>
Now changes in FancyInput automatically update username in the parent!
How $bindable Works
- In the child, mark the prop with
$bindable(defaultValue) - In the parent, use
bind:propName={variable} - Changes flow both directions automatically
When to Use $bindable
Common use cases:
- Custom input components (text fields, sliders, toggles)
- Modal open/close state
- Accordion expanded state
- Any reusable component that controls a value
Important Notes
$bindableprovides a default value if the parent doesn't bind- If the parent passes the prop normally (without
bind:), it acts as a regular prop - The child can modify
$bindableprops directly