A lightweight, type-safe, and reactive form state management hook for Svelte 5, featuring:
npm install svelte-simple-form
Optionally use Zod for validation
npm install zod
Note: This hook is built to work seamlessly with Svelte 5's reactive system, using $state
, $effect
, and tick
. Make sure your project is on Svelte 5 or later.
useForm<T>(props: FormProps<T>)
Creates and returns the reactive form
object managing form state, validation, and events.
Name | Type | Description |
---|---|---|
initialValues |
T Automatically |
Initial values for the form fields. |
validation |
{ zod: schema, relatedFields: Record<string, string[]> } |
Zod schema & related field validation. |
onSubmit |
Optional async callback | Called on successful submission. |
onChange |
Optional callback | Called on any field update. |
onReset |
Optional callback | Called when form resets. |
{
form: {
initialValues: T;
data: T;
errors: Record<Path<T>, string[] | undefined>;
isValid: boolean;
isSubmitting: boolean;
isDirty: boolean;
touched: Record<Path<T>, boolean | undefined>;
setInitialValues(values: T, options?: { reset?: boolean }): void;
setIsDirty(dirty?: boolean): void;
setIsSubmitting(submitting?: boolean): void;
reset(): void;
resetField(field: Path<T>): void;
setError(field: Path<T>, error: string | string[]): void;
validate(field?: Path<T> | Path<T>[]): boolean;
submit(callback?: (data: T) => any): Promise<void>;
handler(node: HTMLFormElement): void;
}
}
setInitialValues(values: T, options?: { reset?: boolean })
setIsDirty(dirty?: boolean)
setIsSubmitting(submitting?: boolean)
reset()
onReset
callback if provided.resetField(field: Path<T>)
setError(field: Path<T>, error: string | string[])
validate(field?: Path<T> | Path<T>[])
true
if form is valid; false
otherwise.submit(callback?: (data: T) => any)
onSubmit
.isSubmitting
state during async submission.handler(node: HTMLFormElement)
submit()
automatically on submit event, preventing default browser submission.Property | Type | Description | |
---|---|---|---|
form.data |
T |
Current form data, bind inputs here. | |
form.errors |
Record<Path<T>, string[] or undefined> |
Validation errors keyed by path. | |
form.isValid |
boolean |
True if form has no validation errors. | |
form.isSubmitting |
boolean |
True if form is currently submitting. | |
form.isDirty |
boolean |
True if form data differs from initial values. | |
form.touched |
Record\<Path<T>, boolean or undefined> |
Tracks which fields have been modified. |
<script lang="ts">
import { useForm } from 'svelte-simple-form';
import { z } from 'zod';
let submitJson = $state('');
const schema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email("This isn't an email"),
age: z.number().min(18, 'Must be at least 18')
});
const { form } = useForm({
initialValues: { name: '', email: '', age: 0 },
validation: { zod: schema },
onSubmit: async (data) => {
submitJson = JSON.stringify(data);
console.log(`Submitted: ${JSON.stringify(data)}`);
},
onChange: (field, value) => {
submitJson = '';
console.log(`Field ${field} changed to`, value);
},
onReset: () => {
console.log('Form was reset');
}
});
function setEmailError() {
form.setError('email', 'Email really exit in db');
}
</script>
<div>
<form use:form.handler>
<!-- user name input -->
<div>
<input type="text" bind:value={form.data.name} placeholder="Name" />
{#if form.errors['name']?.length}
<p>{form.errors['name'][0]}</p>
{/if}
</div>
<!-- user email input -->
<div>
<input type="email" bind:value={form.data.email} placeholder="email" />
{#if form.errors['email']?.length}
<p>{form.errors['email'][0]}</p>
{/if}
</div>
<!-- form handler -->
<div>
<button type="submit" disabled={form.isSubmitting}>
{form.isSubmitting ? 'Submitting...' : 'Submit'}
</button>
<button type="button" onclick={() => form.reset()}> Reset </button>
<button type="button" onclick={() => setEmailError()}> setEmailError </button>
</div>
</form>
<div>
{#if submitJson}
<pre>
{submitJson}
</pre>
{/if}
</div>
</div>
$state
, $effect
, tick
).Path<T>
.onChange
is triggered for every changed field with path and new value.form.isDirty
to track if the user has modified the form.resetField
allows fine-grained reset of individual nested fields.setError
allows manual setting of errors for specific fields.form.handler
directive to bind submit event easily.form.{state} = value
for manually change state valueform.{data|errors|touched}.{field} = value
for manually change state field valueinitialValues
does not support nested string paths (like "body.height"), use objects instead.