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()allowsbind:valuefrom 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.