Forms and two-way binding
Two-way binding keeps an input and a client machine in sync. It’s a client component feature — bind to client state, never server state (you don’t want the network in the typing loop).
Bind an input
Section titled “Bind an input”<input bind:value={draft.query} /><input type="checkbox" bind:checked={draft.agree} />How it desugars
Section titled “How it desugars”bind:value is two halves: a state→DOM write (keeps the input current) and a DOM→state update via the engine’s built-in @set event (assigns one context key on input). You get both directions from one directive.
Loop-break and IME safety
Section titled “Loop-break and IME safety”The writeback is suppressed when the value is unchanged, so the cursor isn’t reset mid-edit, and no update fires while an IME composition is in progress (isComposing). Composed input (CJK, accents) works correctly.
Typed values
Section titled “Typed values”bind:checked reads .checked; an <input type="number"> coerces to a number. You get the native typed value, not a string.
Isomorphic validation
Section titled “Isomorphic validation”A validation selector is a plain function of context, so the same selector runs server- and client-side. Bind its result back into the form:
select: { error: (s) => s.email.includes('@') ? null : 'Invalid email' }<span bind:text={form.error}></span>Custom commit timing
Section titled “Custom commit timing”For commit-on-blur, debounce, or transform-before-store, drop bind:value and wire the two halves yourself:
<input value={read(draft, d => d.query)} on:change={(e) => draft.send({ type: '@set', key: 'query', value: e.target.value })} />