Props & $bindable
Declaring props with $props
Section titled “Declaring props with $props”A component receives inputs from its parent through $props(), which you destructure into the values you expect. This replaces Svelte 4’s export let.
<script> let { name, greeting = 'Hello' } = $props(); // name required, greeting has a default</script>
<p>{greeting}, {name}!</p><!-- parent --><Greeting name="Ada" /><Greeting name="Grace" greeting="Hi" />Destructuring gives you the full toolkit of JavaScript: defaults (greeting = 'Hello'), renaming ({ class: className }), and rest to collect everything else (let { id, ...rest } = $props()), which is perfect for forwarding attributes to an element with {...rest}.
Typing props
Section titled “Typing props”With lang="ts", declare an interface and annotate the destructuring — Svelte type-checks the parent’s usage against it.
<script lang="ts"> interface Props { name: string; greeting?: string; // optional count?: number; } let { name, greeting = 'Hello', count = 0 }: Props = $props();</script>One-way data flow by default
Section titled “One-way data flow by default”Props flow down: a parent passes values, and the child reads them. A child should not reassign a prop to “send data back” — that breaks the one-directional flow and Svelte warns about it. When a child needs to communicate upward, the parent passes a callback prop, or the two share state via binding.
<script> // Parent passes a callback; child calls it — data flows up explicitly. let { onincrement } = $props();</script><button onclick={() => onincrement()}>+1</button>Two-way binding with $bindable
Section titled “Two-way binding with $bindable”Sometimes two-way binding is genuinely the clean design — a custom input component whose value the parent wants to bind: to. Mark that prop $bindable() and the parent can bind to it.
<script> let { value = $bindable() } = $props();</script>
<input bind:value={value} /><!-- parent --><script> let name = $state('');</script><FancyInput bind:value={name} /> <!-- two-way: parent's name stays in sync -->flowchart TB parent["Parent state"] -->|props flow down| child["Child reads props"] child -->|callback prop| parent parent <-->|bind: with $bindable| child
Two-way binding is opt-in on purpose: most props stay one-way (simpler to reason about), and $bindable is reserved for components where syncing a value both ways is the natural interface.