Standardmäßig fließen Props in Svelte nur in eine Richtung: vom Eltern-Komponent zum Kind. Wenn das Kind den Wert verändern und der Eltern-Komponent das automatisch mitbekommen soll, muss eine Prop als zwei-weg-bindbar markiert werden — und zwar explizit. Genau das macht $bindable. Dieser Artikel zeigt, wann du das brauchst, wie es im Vergleich zu einer Callback-Prop aussieht und warum die Explizitheit Absicht ist.
Grundprinzip
<script>
let { value = $bindable(0) } = $props();
</script>
<input type="number" bind:value />Zwei Dinge passieren in dieser einen Zeile:
$bindable(0)markiertvalueals zwei-weg-bindbar. Damit darf der Eltern-Komponent später einbind:valuedarauf nutzen.- Der Wert
0dient als Default — wird gebraucht, wenn der Eltern-Komponent gar keinen Wert setzt.
Innerhalb der Komponente kannst du value jetzt wie eine ganz normale $state-Variable behandeln: lesen, neu zuweisen, in bind:value={value} für ein Form-Element einsetzen.
<script>
import NumberInput from '$lib/components/NumberInput.svelte';
let count = $state(10);
</script>
<NumberInput bind:value={count} />
<p>Aktuell: {count}</p>Wenn der Nutzer im <input> der Kind-Komponente einen neuen Wert eingibt, propagiert das automatisch nach count im Eltern-Komponente.
Mit oder ohne bind:
Eine $bindable-Prop kann zwei-weg-gebunden werden, muss es aber nicht. Wird sie ohne bind: übergeben, verhält sie sich wie eine normale Prop:
<!-- Two-Way-Binding -->
<NumberInput bind:value={count} />
<!-- Read-only — Updates der Kind-Komponente werden ignoriert -->
<NumberInput value={42} />Damit lässt sich $bindable flexibel anbieten, ohne den Aufrufer zu zwingen.
Pflicht-Bindable: ohne Default
Wenn die Komponente immer ein bind:value erwartet, lässt sich der Default weglassen:
<script>
let { value = $bindable() } = $props();
</script>Wer die Komponente ohne bind:value einbindet, bekommt einen Compiler-Hinweis. Das ist sinnvoll, wenn die Komponente ohne externen State sinnlos wäre — z. B. ein Form-Element, das keinen eigenen lokalen Default kennt.
Mehrere bindbare Props
Eine Komponente kann mehrere bindbare Props haben — typisch bei Range-Slidern, Datumsbereichen oder X/Y-Koordinaten:
<script>
let {
start = $bindable(new Date()),
end = $bindable(new Date()),
} = $props();
</script>
<input type="date" bind:value={start} />
<input type="date" bind:value={end} /><DateRange bind:start={from} bind:end={to} />Wann $bindable, wann Callback-Prop?
| Aspekt | $bindable | Callback-Prop |
|---|---|---|
| Aufruf am Eltern-Komponent | bind:value={count} | value={count} onChange={(v) => count = v} |
Eltern muss $state halten | Ja | Ja |
| Kind kann Wert direkt mutieren | Ja | Nein, nur Callback ausführen |
| Implizit im Markup | Ja | Explizit |
| Einfacher zu typisieren | Ja | Etwas verbose |
| Ausdruck im Eltern komplexer | Schwierig | Sehr flexibel (Validierung, Side Effects) |
Faustregel: Für Form-Inputs, Picker und ähnliche „dumme” Werte-Inhaber ist $bindable ergonomischer. Sobald komplexe Update-Logik im Eltern nötig ist (Validierung, Throttling, parallele Updates), liefert eine Callback-Prop mehr Kontrolle.
TypeScript-Typen für bindable Props
Auch $bindable lässt sich sauber typisieren:
<script lang="ts">
type Props = {
value?: number;
step?: number;
};
let { value = $bindable(0), step = 1 }: Props = $props();
</script>
<input type="number" bind:value step={step} />Wichtig: Im Type-Annotation ist die Prop optional (value?: number), weil sie einen Default hat. Ohne Default und mit Pflicht-Bindable wäre value: number und value = $bindable().
Migration von export let mit Bind
In Svelte 4 war jede export let-Variable implizit bindbar. Eltern-Komponenten konnten bind:value={...} nutzen, ohne dass die Kind-Komponente das markieren musste. In Svelte 5 ist das explizit: Nur Props, die mit $bindable(...) markiert sind, können zwei-weg-gebunden werden.
<script>
export let value = 0;
</script>
<input type="number" bind:value /><script>
let { value = $bindable(0) } = $props();
</script>
<input type="number" bind:value />Die explizite Markierung erleichtert das API-Verständnis: Auf einen Blick sichtbar, welche Props bidirektional sind.
Häufige Stolperfallen
bind:value ohne $bindable in der Kind-Komponente.
Compiler-Fehler. Die Prop muss explizit markiert sein.
$bindable mit nicht-mutierbarer Quelle im Eltern.
<NumberInput bind:value={42} />42 ist kein State — es lässt sich nicht zurückschreiben. Im Eltern muss eine $state-Variable stehen.
$bindable für berechnete Werte.
Wenn der Wert im Eltern eigentlich eine Ableitung ist, passt $bindable nicht. Stattdessen die Quelle reaktiv halten und ableiten.
$bindable mit komplexen Objekten.
Funktioniert, wenn das Objekt selbst reaktiv ist ($state). Bei nicht-reaktiven Objekten verlieren tiefe Mutationen ihre Wirkung.