Skip to content

Props & $bindable

A component receives inputs from its parent through $props(), which you destructure into the values you expect. This replaces Svelte 4’s export let.

Greeting.svelte
<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}.

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>

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>

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.

FancyInput.svelte
<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
Props flow down; $bindable opens a two-way channel

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.

How does a Svelte 5 component declare its props?
How do you give a prop a default value?
By default, how does data flow with props?
How do you allow a parent to `bind:` to a child prop?