Svelte and SvelteKit course ยท Module 4: Props and Events
Two-Way Bindings ($bindable)
In this lesson4
Sometimes you need a child to be able to modify the parent's data. Use $bindable!
$bindable in Svelte 5
<!-- SearchInput.svelte -->
<script>
let { value = $bindable('') } = $props();
</script>
<input bind:value placeholder="Search warrior..." /><!-- Parent.svelte -->
<script>
import SearchInput from './SearchInput.svelte';
let searchQuery = $state('');
</script>
<SearchInput bind:value={searchQuery} />
<p>Searching: {searchQuery}</p>When to Use $bindable?
Good cases:
- Form inputs
- Controlled components (select, slider)
- Modal with visibility control
Avoid when:
- One-way data flow is sufficient
- Complex update logic
- Multiple sources of truth
Example: Slider
<!-- VolumeSlider.svelte -->
<script>
let { value = $bindable(50), min = 0, max = 100 } = $props();
</script>
<div class="slider">
<input
type="range"
bind:value
{min}
{max}
/>
<span>{value}</span>
</div>
<style>
.slider {
display: flex;
align-items: center;
gap: 1rem;
}
</style><!-- Parent.svelte -->
<script>
import VolumeSlider from './VolumeSlider.svelte';
let warHornVolume = $state(75);
</script>
<h3>War horn volume</h3>
<VolumeSlider bind:value={warHornVolume} />
<p>Current volume: {warHornVolume}%</p>Example: Modal
<!-- Modal.svelte -->
<script>
let { open = $bindable(false), title, children } = $props();
function close() {
open = false;
}
</script>
{#if open}
<div class="overlay" role="presentation" onclick={close}>
<div
class="modal"
role="dialog"
aria-modal="true"
tabindex="-1"
onclick={(e) => e.stopPropagation()}
onkeydown={(e) => e.key === 'Escape' && close()}
>
<header>
<h2>{title}</h2>
<button onclick={close}>ร</button>
</header>
<main>
{@render children()}
</main>
</div>
</div>
{/if}A click on the background closes the window, and e.stopPropagation() (formerly the |stopPropagation modifier) keeps a click inside the window from reaching the background.
<!-- Parent.svelte -->
<script>
import Modal from './Modal.svelte';
let showModal = $state(false);
</script>
<button onclick={() => showModal = true}>
Open modal
</button>
<Modal bind:open={showModal} title="War council">
<p>We are planning an attack at dawn!</p>
</Modal>Code for this lesson: App.svelte
<script>
// $bindable: the child changes the parent's state through bind:value
import CoinCounter from './CoinCounter.svelte';
let treasury = $state({ gold: 10, silver: 25, copper: 40 });
let worth = $derived(treasury.gold * 100 + treasury.silver * 10 + treasury.copper);
</script>
<main class="treasury">
<h1>Viking Treasury</h1>
<p>$bindable props: each counter writes straight into the chest</p>
<div class="counters">
<CoinCounter name="Gold" bind:value={treasury.gold} />
<CoinCounter name="Silver" bind:value={treasury.silver} />
<CoinCounter name="Copper" bind:value={treasury.copper} />
</div>
<div class="total">
<h2>Worth in copper: {worth}</h2>
<button onclick={() => (treasury = { gold: 0, silver: 0, copper: 0 })}>
Empty the chest
</button>
</div>
</main>
<style>
.treasury {
font-family: 'Georgia', serif;
max-width: 600px;
margin: 0 auto;
padding: 2rem;
background: #1a0f0a;
color: #e8d5b7;
min-height: 100vh;
}
h1 {
color: #d4af37;
text-align: center;
}
p {
text-align: center;
color: #8b7355;
}
.counters {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
margin: 2rem 0;
}
.total {
text-align: center;
background: rgba(212, 175, 55, 0.2);
border: 2px solid #d4af37;
border-radius: 8px;
padding: 1rem;
}
.total h2 {
color: #ffd700;
margin: 0 0 0.75rem 0;
}
.total button {
padding: 0.5rem 1rem;
background: #3d2914;
color: #e8d5b7;
border: 1px solid #d4af37;
border-radius: 4px;
cursor: pointer;
}
</style>Docs entries used in this lesson
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. How do you mark a prop as bindable in Svelte 5?
Hands-on tasks in the game
- Code editor
Complete the code so that value is bindable with a default value of ''