MonoStack

@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

On this page