Form
Typed form scaffolding on formsnap + sveltekit-superforms. Label, Control, Description, and FieldErrors compose around a bloom control while the form owns validation and accessibility wiring.
Usage
Form is a thin wrapper over formsnap and sveltekit-superforms. Feed it a superforms instance
from a SvelteKit +page.svelte load and render fields
with typed name paths. Errors and aria wiring come for free.
Form requires a live superForm instance from a SvelteKit route. See the Code tab for a minimal end-to-end example.
<script lang="ts">
import { superForm } from 'sveltekit-superforms';
import * as Form from '$lib/components/ui/form';
import { Input } from '$lib/components/ui/input';
let { data } = $props();
const form = superForm(data.form);
const { form: formData, enhance } = form;
</script>
<form method="POST" use:enhance class="flex w-full max-w-sm flex-col gap-4">
<Form.Field {form} name="email">
<Form.Control>
{#snippet children({ props })}
<Form.Label>Email</Form.Label>
<Input {...props} type="email" bind:value={$formData.email} />
{/snippet}
</Form.Control>
<Form.Description>Used for sign-in only.</Form.Description>
<Form.FieldErrors />
</Form.Field>
<Form.Field {form} name="name">
<Form.Control>
{#snippet children({ props })}
<Form.Label>Display name</Form.Label>
<Input {...props} bind:value={$formData.name} />
{/snippet}
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<Form.Button>Save</Form.Button>
</form>Form.Field props
Thin wrapper over formsnap's Field. name is typed against the form schema via generics.
| Prop | Type | Default | Description |
|---|---|---|---|
SuperForm<T> | — | The superforms instance returned by superForm(data.form). | |
FormPath<T> | — | Typed field path. Intellisense is derived from the form schema passed to superForm. | |
string | — | Merged onto the wrapper via tailwind-merge. |
Form.Control usage
Form.Control exposes a child snippet with props that wire the associated control to the label and error region.
| Prop | Type | Default | Description |
|---|---|---|---|
Snippet<{ props: Record<string, unknown> }> | — | Render the control inside and spread props onto it. |
Form.FieldErrors props
Renders scoped error messages for the enclosing field. Variables bind straight from formsnap context.
| Prop | Type | Default | Description |
|---|---|---|---|
string | null | undefined | — | Optional class applied to each error row when the default renderer is used. | |
Snippet<{ errors: string[]; errorProps: Record<string, unknown> }> | — | Custom rendering. Override when you need icons, links, or non-standard layout. | |
string | — | Merged onto the container via tailwind-merge. |
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
import * as FormPrimitive from 'formsnap';
import type { FormPath } from 'sveltekit-superforms';
import { cn, type WithElementRef, type WithoutChildren } from '$lib/utils.js';
import type { HTMLAttributes } from 'svelte/elements';
let {
ref = $bindable(null),
class: className,
form,
name,
children: childrenProp,
...restProps
}: FormPrimitive.FieldProps<T, U> &
WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> = $props();
</script>
<FormPrimitive.Field {form} {name}>
{#snippet children({ constraints, errors, tainted, value })}
<div bind:this={ref} data-slot="form-item" class={cn('space-y-2', className)} {...restProps}>
{@render childrenProp?.({ constraints, errors, tainted, value: value as T[U] })}
</div>
{/snippet}
</FormPrimitive.Field>