@repo/form
Schema-driven form generation for Svelte 5 with automatic field rendering from Zod schemas.
Quick Start
Define the schema
import { z } from 'zod'
const schema = z.object({
username: z.string(),
})For remote functions. Read more about TypedForm.Remote
<TypedForm.Remote form={createUserForm} {schema} />Remote forms reset client state to schema defaults when unmounted. Use Zod .default(...) for non-empty create defaults; fields without defaults clear.
Set disabled to wrap the fields in a disabled fieldset and hide the submit button.
Zod number fields use numeric inputs and submit numbers to remote forms.
For client functions. Read more about TypedForm.Client
<TypedForm.Client bind:value {schema} onsubmit={handleSubmit} />Render a schema as a read-only form by passing disabled props through the generated fields:
<fieldset disabled>
<TypedForm.Client {value} {schema}>
{#snippet children({ fields, value: formValue })}
<TypedForm.Fields {fields} value={formValue} disabled readonly />
{/snippet}
{#snippet button()}{/snippet}
</TypedForm.Client>
</fieldset>Customize fields with the input prop:
<TypedForm.Remote form={createUserForm} {schema}>
{#snippet children({ fields })}
<TypedForm.RemoteField field={fields.username} />
<TypedForm.RemoteField field={fields.password} input={TypedForm.TextInput} type="password" />
{/snippet}
</TypedForm.Remote>Custom submit buttons receive submission and dirty state. Dirty state resets after a successful submit.
<TypedForm.Remote form={updateUserForm} {schema}>
{#snippet button({ isDirty, submitting })}
<SubmitButton {submitting} disabled={!isDirty} />
{/snippet}
</TypedForm.Remote>The fields and client-form value snippet parameters are inferred from schema; call sites do not need to annotate them.
Fields use their schema label, or a label generated from the accessor. Pass label={false} when a surrounding UI, such as a table header, already provides the accessible label.
Use ChoiceBooleanField for a yes/no button field:
<TypedForm.RemoteField field={fields.isAgencyWorker} input={TypedForm.ChoiceBooleanField} />File fields
z.file() renders the standard single-file input. z.array(z.file()) renders it in multiple mode.
Optional, nullable, and default wrappers are inspected automatically.
const schema = z.object({
source: z.file(),
attachments: z.array(z.file()).min(1).max(10),
})<TypedForm.Remote form={uploadForm} {schema} />Schema-backed Remote and Client forms infer enctype="multipart/form-data" when any field contains
a file. Explicit enctype values are preserved. Use zaf() metadata or a RemoteField input prop
to replace the standard file component.
Utilities
getFormFieldByPath(accessor: string)
Retrieves a form field and its parent remote form from context by dot-notation path. Must be called inside a TypedForm.Remote component tree.
<script lang="ts">
import { getFormFieldByPath } from '@repo/form'
const { formField, remoteForm } = getFormFieldByPath('domain')
</script>
<select {...formField.as('select')}> ... </select>Docs
- Remote vs Client - Which to use
- Remote Forms - Server actions with
formfrom$app/server - Client Forms - Client-side only with
bind:value - Customization - Custom layouts, full control mode