2 Forms
L4stIdi0t edited this page 2026-09-29 12:21:07 +02:00

Forms

Every control mirrors its state into a hidden input when you give it a name, so a plain <form> — or a SvelteKit form action — sees it with no wiring:

Control Submits
Checkbox, Switch, Toggle the value (default "on") when on, and nothing at all when off, exactly like a native checkbox
Select, Combobox, RadioGroup the chosen value
MultiSelect, TagsInput one field per selected value — read it with formData.getAll(name)
Slider, NumberInput the current number, always present
<form method="POST" action="?/save">
	<Field label="Snapshot name" hint="Shown in the sidebar." error={form?.errors?.name}>
		{#snippet control(props)}
			<Input {...props} name="name" value={form?.name ?? ''} />
		{/snippet}
	</Field>

	<Checkbox name="hidden_files" checked />
	<MultiSelect name="tags" options={tagOptions} values={tags} aria-label="Tags" />

	<Button type="submit">Save</Button>
</form>

Labels, hints and errors

Field is the piece that is easy to get wrong by hand. It renders the label, the hint and the error, and hands your control the props that connect them: id, aria-describedby pointing at both the hint and the error, and aria-invalid when there is one. A red message with no aria-describedby is invisible to a screen reader — which is the usual outcome of wiring this by hand.

Anything that takes native attributes (Input, Textarea, Select, MultiSelect, Combobox, …) accepts those props with a spread, so {...props} is the whole integration.

Continuous controls

Slider and NumberInput fire two callbacks. onValueChange is live — every step of a drag, every keystroke that parses. onValueCommit fires when the user settles: pointer release, blur, Enter, or a keyboard step. Anything expensive belongs on the commit channel; the live one is for a preview and a number on screen.

NumberInput clamps on commit, never per keystroke, so typing "12" on the way to "120" is not rewritten under your fingers.