Callback props and $bindable

Svelte · Practical · 11 min

Components need to report what happened (selection, submit, close) without coupling to their parents.

In Svelte 5 a child communicates upward by calling callback props, plain functions passed by the parent. There is no special event system: name them like events (onselect, onclose) and call them:

let { onselect } = $props();
// later
onselect?.(item);

The parent passes <Picker onselect={(item) => (chosen = item)} />. Callbacks can take any arguments and return values, and TypeScript types them like any function prop. (Svelte 4's createEventDispatcher and on:event are deprecated.)

For form-like components, a bindable prop gives two-way binding. The child marks the prop with $bindable():

let { value = $bindable('') } = $props();

The parent then may write <TextInput bind:value={query} />, and assignments to value in the child flow back to query. Without bind:, the prop behaves like a normal one-way prop. The argument to $bindable() is the fallback when the parent passes nothing.

Use bindable props sparingly: they are great for input-like components (text fields, toggles, sliders) but make data flow harder to trace elsewhere. For most interactions, an explicit callback is clearer.

Forwarding is easy: since handlers are props, ...rest spread on an element passes onclick and friends through to the DOM.

Example

<!-- Toggle.svelte -->
<script>
  let { on = $bindable(false), label, onchange } = $props();
  function flip() {
    on = !on;                      // flows back to the parent if bound
    onchange?.(on);                // tell the parent what happened
  }
</script>
<button aria-pressed={on} onclick={flip}>{label}: {on ? 'on' : 'off'}</button>

<!-- App.svelte -->
<script>
  import Toggle from './Toggle.svelte';
  let dark = $state(false);
  let history = $state([]);
</script>

<Toggle label="Dark mode" bind:on={dark}
        onchange={(v) => history.push(v ? 'enabled' : 'disabled')} />
<p>dark = {dark}; {history.join(', ')}</p>

Remember

  • Children call callback props (onselect?.(x)) to notify parents
  • let { value = $bindable() } = $props() allows bind:value from the parent
  • Prefer callbacks; reserve bindable props for input-like components

Watch out for

  • Using bind: on a prop that wasn't declared with $bindable(); Svelte reports an error.

Check yourself

  1. A child declares `let { open = $bindable(false) } = $props()`. The parent writes `<Dialog open={show} />` (no bind:). The child sets open = true. What happens to show?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: svelte.dev/docs/svelte/$bindable

Builds on

Unlocks

Nothing builds on this yet.