Svelte and SvelteKit course ยท Module 4: Props and Events

Two-Way Bindings ($bindable)

2 min read ยท
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. 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 ''

Useful articles