A lightweight, type-safe, and reactive form state management hook for Svelte 5, featuring:
- Nested field paths support
- Validation integration with Zod
- Dirty tracking, touched fields, and submission state
- Minimal dependencies & boilerplate — designed for Svelte 5’s new reactive primitives
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.
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;
}
}
- Set new initial values for the form.
- Optionally reset the current form data to the new initial values.
- Manually mark the form as dirty or clean.
- Manually set submitting state (e.g., show spinner).
- Reset form data to initial values.
- Clear errors and touched fields.
- Calls
onReset
callback if provided.
- Reset a single field (and its nested children) to its initial value.
- Clears touched state for the reset field.
- Manually set an error for a specific field.
- Like server from check email really exit in db
- Run validation on the entire form or specific fields using Zod.
- Clears errors on validated fields and sets new errors if any.
- Returns
true
if form is valid;false
otherwise.
- Perform validation (if configured).
- If valid, calls provided callback or
onSubmit
. - Manages
isSubmitting
state during async submission.
- Attach a native submit event listener to a form element.
- Calls
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>
- Designed specifically for Svelte 5, leveraging its reactive primitives (
$state
,$effect
,tick
). - Supports deeply nested objects and arrays with full type safety via
Path<T>
. - Validation is optional but highly recommended using Zod.
-
onChange
is triggered for every changed field with path and new value. - Use
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. - Use
form.handler
directive to bind submit event easily. - Use
form.{state} = value
for manually change state value - Use
form.{data|errors|touched}.{field} = value
for manually change state field value -
initialValues
does not support nested string paths (like "body.height"), use objects instead.