Form-Bindings sind der häufigste Anwendungsfall für bind: und einer der größten Ergonomie-Vorteile von Svelte. Statt für jedes Eingabefeld value= und onchange= zu schreiben, reicht eine einzige Direktive. Dieser Artikel deckt alle Form-Bindings systematisch ab — Text, Number, Textarea, Select, Checkbox, Radio, Group und File-Inputs.
Text-Inputs und Textareas
<script>
let name = $state('');
</script>
<input type="text" bind:value={name} />
<p>Hallo, {name || '(noch nichts eingegeben)'}</p>bind:value arbeitet identisch mit <textarea>:
<script>
let bio = $state('');
</script>
<textarea bind:value={bio} rows="4" placeholder="Bio..."></textarea>
<p>{bio.length} Zeichen</p>Number- und Range-Inputs
Bei type="number" und type="range" liefert bind:value automatisch eine Zahl statt eines Strings:
<script>
let age = $state(30);
</script>
<input type="number" bind:value={age} min="0" max="120" />
<p>{age + 1} (nächstes Jahr)</p>age + 1 funktioniert ohne Konvertierung — der Wert ist tatsächlich number.
<script>
let volume = $state(50);
</script>
<input type="range" bind:value={volume} min="0" max="100" />
<p>Lautstärke: {volume}%</p>Bei leerem <input type="number"> ist der Wert null (nicht '').
Checkbox
Eine einzelne Checkbox bindest du an einen Boolean:
<script>
let acceptTerms = $state(false);
</script>
<label>
<input type="checkbox" bind:checked={acceptTerms} />
Allgemeine Geschäftsbedingungen akzeptieren
</label>
<button disabled={!acceptTerms}>Weiter</button>Indeterminate-Zustand
Für die dritte Checkbox-Stellung („teilweise selektiert”):
<script>
let allSelected = $state(false);
let someSelected = $state(true);
</script>
<input
type="checkbox"
bind:checked={allSelected}
bind:indeterminate={someSelected}
/>bind:indeterminate ist unabhängig von bind:checked — beide können gleichzeitig wahr sein.
Radio-Gruppe mit bind:group
Bei einer Gruppe von Radio-Buttons hilft bind:group — die Variable enthält den Wert der aktuell ausgewählten Option:
<script>
let plan = $state('free');
</script>
<fieldset>
<legend>Tarif</legend>
<label>
<input type="radio" bind:group={plan} value="free" />
Kostenlos
</label>
<label>
<input type="radio" bind:group={plan} value="pro" />
Pro
</label>
<label>
<input type="radio" bind:group={plan} value="team" />
Team
</label>
</fieldset>
<p>Gewählt: {plan}</p>Wichtig: Alle Radios mit demselben bind:group gehören automatisch zur selben Gruppe — kein eigenes name-Attribut nötig.
Checkbox-Gruppe mit bind:group
Bei Checkboxen hält bind:group ein Array der ausgewählten Werte:
<script>
let interests = $state([]);
</script>
<fieldset>
<legend>Interessen</legend>
<label>
<input type="checkbox" bind:group={interests} value="sport" />
Sport
</label>
<label>
<input type="checkbox" bind:group={interests} value="musik" />
Musik
</label>
<label>
<input type="checkbox" bind:group={interests} value="kunst" />
Kunst
</label>
</fieldset>
<p>Auswahl: {interests.join(', ') || '(keine)'}</p>interests enthält z. B. ['sport', 'kunst'] und aktualisiert sich automatisch bei jeder Änderung.
Select – einfach und mehrfach
Single-Select
<script>
let country = $state('de');
</script>
<select bind:value={country}>
<option value="de">Deutschland</option>
<option value="at">Österreich</option>
<option value="ch">Schweiz</option>
</select>
<p>Ausgewählt: {country}</p>option-Werte können beliebige JavaScript-Werte sein — auch Objekte. Svelte vergleicht sie per Identität:
<script>
const countries = [
{ code: 'de', name: 'Deutschland' },
{ code: 'at', name: 'Österreich' },
];
let selected = $state(countries[0]);
</script>
<select bind:value={selected}>
{#each countries as country (country.code)}
<option value={country}>{country.name}</option>
{/each}
</select>
<p>Code: {selected.code}</p>Multi-Select
Mit dem multiple-Attribut wird die Variable zu einem Array:
<script>
let selected = $state([]);
</script>
<select multiple bind:value={selected}>
<option value="rot">Rot</option>
<option value="gruen">Grün</option>
<option value="blau">Blau</option>
</select>
<p>Auswahl: {selected.join(', ')}</p>File-Inputs mit bind:files
Datei-Inputs liefern eine FileList über bind:files:
<script>
let files = $state(null);
function upload() {
if (!files) return;
for (const file of files) {
console.log(file.name, file.size, file.type);
}
}
</script>
<input type="file" multiple bind:files />
{#if files}
<ul>
{#each Array.from(files) as file}
<li>{file.name} ({(file.size / 1024).toFixed(1)} KB)</li>
{/each}
</ul>
<button onclick={upload}>Hochladen</button>
{/if}files ist eine FileList (kein Array). Für map/filter mit Array.from(files) konvertieren.
Bindings sind in beiden Richtungen aktiv: Eine Zuweisung files = null setzt das Eingabefeld zurück.
TypeScript: Form-Bindings sauber typisieren
<script lang="ts">
type Plan = 'free' | 'pro' | 'team';
let name = $state('');
let age = $state<number | null>(null);
let plan = $state<Plan>('free');
let interests = $state<string[]>([]);
let files = $state<FileList | null>(null);
</script>
<input bind:value={name} />
<input type="number" bind:value={age} />Achtung: <input type="number"> bei leerer Eingabe gibt null zurück — der Type sollte das abbilden (number | null).
Praxis-Beispiel: Anmeldeformular
<script>
let form = $state({
email: '',
password: '',
role: 'user',
receivesNewsletter: false,
interests: [],
});
let isValid = $derived(
form.email.includes('@') &&
form.password.length >= 8
);
function submit(event) {
event.preventDefault();
console.log(form);
}
</script>
<form onsubmit={submit}>
<label>
E-Mail
<input type="email" bind:value={form.email} required />
</label>
<label>
Passwort
<input type="password" bind:value={form.password} required />
</label>
<label>
Rolle
<select bind:value={form.role}>
<option value="user">Standard</option>
<option value="admin">Admin</option>
</select>
</label>
<label>
<input type="checkbox" bind:checked={form.receivesNewsletter} />
Newsletter abonnieren
</label>
<fieldset>
<legend>Interessen</legend>
<label>
<input type="checkbox" bind:group={form.interests} value="dev" />
Entwicklung
</label>
<label>
<input type="checkbox" bind:group={form.interests} value="design" />
Design
</label>
</fieldset>
<button type="submit" disabled={!isValid}>Registrieren</button>
</form>Alle Eingaben werden automatisch in form synchronisiert. Validierung und Disabled-Zustand laufen über $derived.
Häufige Stolperfallen
bind:value mit value="..." mischen.
Beides gleichzeitig führt zu undefiniertem Verhalten. Bei bind: schreibt Svelte selbst — kein zusätzliches value=.
Numerischer Input mit String-Erwartung.
bind:value bei type="number" liefert eine Zahl. Bei type="text" einen String — auch wenn der Nutzer Ziffern eingibt.
bind:group mit Variable außerhalb der <fieldset>.
Funktioniert, ist aber semantisch unklar. Lieber Form-State zentral in einem Objekt halten und Felder gruppieren.
Datei-Input nicht zurücksetzen.
Manche Browser akzeptieren files = null nicht zuverlässig; in dem Fall <input type="file" {key}> mit wechselndem key als Reset-Trigger nutzen.
Konflikt von bind:checked und bind:group auf demselben Input.
Pro Input nur eine der beiden — sonst ist das Verhalten unklar.