Forms & Inputs
Controlled inputs with bind:
Section titled “Controlled inputs with bind:”Forms bring bindings, runes, and events together. Each field binds to state; validation derives from that state; submit reads it. Here’s the shape of a real form:
<script> let email = $state(''); let password = $state('');
// Validation is DERIVED from the fields — never stored separately. let emailValid = $derived(/^[^@]+@[^@]+\.[^@]+$/.test(email)); let passwordValid = $derived(password.length >= 8); let formValid = $derived(emailValid && passwordValid);</script>
<input type="email" bind:value={email} placeholder="Email" />{#if email && !emailValid}<small>Enter a valid email</small>{/if}
<input type="password" bind:value={password} placeholder="Password" />{#if password && !passwordValid}<small>At least 8 characters</small>{/if}The key idea: validation is derived, not stored. emailValid recomputes automatically whenever email changes — there’s no “revalidate” step to forget. This is the “derive, don’t sync” principle from the effects lesson, applied to forms.
Handling submit
Section titled “Handling submit”Attach onsubmit to the <form> and call e.preventDefault() to stop the browser’s native navigation, then act on the collected state:
<script> let submitting = $state(false);
async function handleSubmit(e) { e.preventDefault(); // Svelte 5 has no on:submit|preventDefault modifier if (!formValid) return; submitting = true; await createAccount({ email, password }); submitting = false; }</script>
<form onsubmit={handleSubmit}> <!-- fields… --> <button type="submit" disabled={!formValid || submitting}> {submitting ? 'Creating…' : 'Sign up'} </button></form>Note the disabled button derives from formValid and submitting — the UI stays correct without any manual toggling.
flowchart LR input["inputs (bind:value)"] --> state["field state"] state --> valid["$derived validation"] valid --> button["submit button enabled/disabled"] button --> submit["onsubmit → preventDefault → act"]
Client-side is only half the story
Section titled “Client-side is only half the story”This lesson handles forms entirely on the client — fine for local state and simple cases. But real apps need the data on a server: persistence, auth, validation you can trust. That’s what SvelteKit form actions (a later module) provide — progressive-enhancement forms that work with and without JavaScript, validated on the server. Client-side bind: + $derived gives you responsive UX; SvelteKit gives you the full-stack pipeline.