05 - Bindable ($bindable)
📋 Jump to Takeaways🎁 A parent passes count to a child, the child changes it, and the parent's copy updates too, both directions from a single line. When would you actually want data to flow backward like that?
What is $bindable?
$bindable() creates two-way bindable props. Allows parent components to bind to child component props.
In Svelte 4, every export let prop was implicitly bindable. In Svelte 5, you must explicitly mark which props support two-way binding using $bindable() inside $props().
Svelte 4 (Legacy)
<!-- Child.svelte -->
<script>
export let value = 0; // every export let was implicitly bindable
</script>Svelte 5
<!-- Child.svelte -->
<script>
let { value = $bindable(0) } = $props(); // explicitly opt-in to binding
</script>The parent side stays the same in both versions: <Child bind:value={count} />
Basic Usage
Child Component:
<script>
let { value = $bindable(0) } = $props();
</script>
<input type="number" bind:value />Parent Component:
<script>
import Child from './Child.svelte';
let count = $state(0);
</script>
<Child bind:value={count} />
<p>Parent sees: {count}</p>With Objects
Child:
<script>
let { user = $bindable({ name: '', age: 0 }) } = $props();
</script>
<input bind:value={user.name} />
<input type="number" bind:value={user.age} />Parent:
<script>
let user = $state({ name: 'Alice', age: 25 });
</script>
<Child bind:user />Optional Bindable
Calling $bindable() with no default means value is undefined if the parent doesn't bind to it. Compare:
$bindable(0)defaults to0if not bound$bindable()defaults toundefinedif not bound
Useful when a component optionally supports two-way binding.
<script>
let { value = $bindable() } = $props();
// value is undefined if parent doesn't use bind:value
</script>Interactive Example
Child Component (05-bindable-child.svelte):
<script>
// Child component with bindable value
let { value = $bindable(0) } = $props();
</script>
<div class="input-group">
<button onclick={() => value--}>-</button>
<input type="number" bind:value />
<button onclick={() => value++}>+</button>
</div>
<style>
.input-group {
display: flex;
gap: 5px;
align-items: center;
}
input {
width: 80px;
text-align: center;
}
</style>Parent Component (05-bindable.svelte):
<script>
import Child from './05-bindable-child.svelte';
let count = $state(10);
let doubled = $derived(count * 2);
</script>
<div>
<h2>05 - Bindable Example</h2>
<p>Parent count: {count}</p>
<p>Doubled: {doubled}</p>
<h3>Child component (two-way bound):</h3>
<Child bind:value={count} />
<p>Changes in child automatically update parent!</p>
</div>
<style>
div { margin: 20px; }
h3 { margin-top: 20px; }
</style>Key Takeaways
- Creates two-way binding between parent and child
- Use sparingly. Prefer one-way data flow
- Replaces Svelte 4's implicit binding on
export let. You now explicitly opt in with$bindable() - Parent uses
bind:prop={variable}(same as Svelte 4) - Child must wrap the prop default with
$bindable()forbind:to work
🎁 You can pass values into a component. But what about passing a whole chunk of markup, like a custom row template or a card layout, for the component to render wherever it wants? How does Svelte hand blocks of UI around?