Remote Forms
For forms that submit to server actions using form from $app/server.
Define the Remote Function
// user.remote.ts
import { form } from '$app/server'
import { z } from 'zod/v4'
export const userSchema = z.object({
name: z.string(),
email: z.string().email()
})
export const createUserForm = form(userSchema, async (data) => {
await db.insert(users).values(data)
redirect(303, '/users')
})
Basic Usage
<script lang="ts">
import { TypedForm } from '@repo/form'
import { createUserForm, userSchema } from './user.remote'
</script>
<TypedForm.Remote form={createUserForm} schema={userSchema} />
Pre-populate for Edit
Fetch data and set it on the form before rendering.
<script lang="ts">
import { TypedForm } from '@repo/form'
import { updateUserForm, getUserQuery, userSchema } from './user.remote'
const { params } = $props()
const user = await getUserQuery({ id: params.id })
updateUserForm.fields.set(user)
</script>
<TypedForm.Remote form={updateUserForm} schema={userSchema} />
No-Args Form
For forms without input validation (e.g., delete buttons).
// user.remote.ts
export const deleteUserForm = form(async () => {
const { id } = await getValidatedParams(z.object({ id: z.string() }))
await db.delete(users).where(eq(users.id, id))
redirect(303, '/users')
})
<TypedForm.Remote form={deleteUserForm}>
{#snippet button({ submitting })}
<Button variant="destructive" disabled={submitting}>Delete</Button>
{/snippet}
</TypedForm.Remote>
Remote functions shortcomings
Some usual implementations are not yet implemented how you expect them in the remote functions. We have implemented some helpers:
Is Dirty
To see if the TypedForm is touched
<script lang="ts">
import { TypedForm } from '@repo/form'
import { createUserForm, userSchema } from './user.remote'
let isDirty = $state(false)
</script>
<TypedForm.Remote form={createUserForm} schema={userSchema} bind:isDirty />
Dynamic content based on values
Sometimes the reactivity is not working like you expect. Because we generate the form, the field values are not reactive. We expect this to change, but now you can use.
<script lang="ts">
import { TypedForm } from '@repo/form'
import { createUserForm, userSchema } from './user.remote'
let username = $state('')
</script>
<h1>Your username: {username}</h1>
<TypedForm.Remote form={createUserForm} schema={userSchema}>
{#snippet ({ fields })}
<TypedForm.RemoteField field={fields.username} bind:value={username} />
{/snippet}
</TypedForm.Remote>