Making SvelteKit forms a pleasure to use with comprehensive validation, type safety, and progressive enhancement.
—
Reactive form management with Svelte stores, real-time validation, error handling, progressive enhancement, and comprehensive form state management for SvelteKit applications.
The core client-side function that creates a reactive form management system with Svelte stores, handling validation, submission, error management, and user interactions.
/**
* Creates a reactive form management system with Svelte stores
* @param form - SuperValidated form data from server
* @param options - Configuration options for form behavior
* @returns SuperForm instance with stores and methods
*/
function superForm<T, M>(
form: SuperValidated<T, M>,
options?: FormOptions<T, M>
): SuperForm<T, M>;
interface SuperForm<T, M> {
/** Enhanced form data store with custom set/update methods supporting taint options */
form: SuperFormData<T>;
/** Form ID as writable store */
formId: Writable<string>;
/** Enhanced errors store with clear method and validation-aware updates */
errors: SuperFormErrors<T>;
/** Writable store containing HTML input constraints */
constraints: Writable<InputConstraints<T>>;
/** Writable store for user messages */
message: Writable<M | undefined>;
/** Readable store tracking which fields have been modified */
tainted: Writable<TaintedFields<T> | undefined>;
/** Readable store indicating if form is currently submitting */
submitting: Readable<boolean>;
/** Readable store indicating if submission is delayed */
delayed: Readable<boolean>;
/** Readable store indicating if submission has timed out */
timeout: Readable<boolean>;
/** Readable store indicating if form has been posted (deprecated) */
posted: Readable<boolean>;
/** Flattened errors in array format for easier consumption */
allErrors: Readable<{ path: string; messages: string[] }[]>;
/** Form configuration options */
options: FormOptions<T, M>;
/** SvelteKit enhance function for progressive enhancement */
enhance: (el: HTMLFormElement, events?: SuperFormEvents<T, M>) => ReturnType<SubmitFunction>;
/** Check if form or specific field is tainted */
isTainted: (path?: FormPath<T> | Record<string, unknown> | boolean | undefined) => boolean;
/** Function to reset form to initial state */
reset: (options?: ResetOptions<T>) => void;
/** Programmatically submit the form */
submit: (submitter?: HTMLElement | Event | EventTarget | null) => void;
/** Validate specific field and return errors */
validate: <Path extends FormPathLeaves<T>>(
path: Path,
opts?: ValidateOptions<FormPathType<T, Path>, Partial<T>, Record<string, unknown>>
) => Promise<string[] | undefined>;
/** Validate entire form and return complete result */
validateForm: <P extends Partial<T> = T>(opts?: {
update?: boolean;
schema?: ValidationAdapter<P>;
focusOnError?: boolean;
}) => Promise<SuperFormValidated<T, M>>;
/** Capture current form state for persistence */
capture: () => SuperFormSnapshot<T, M>;
/** Restore form from captured snapshot */
restore: (snapshot: SuperFormSnapshot<T, M>) => void;
}Usage Examples:
<script lang="ts">
import { superForm } from "sveltekit-superforms/client";
import type { PageData } from "./$types.js";
export let data: PageData;
const { form, errors, constraints, message, submitting, enhance } = superForm(data.form, {
resetForm: true,
taintedMessage: "Are you sure you want to leave? You have unsaved changes.",
scrollToError: 'smooth',
autoFocusOnError: true,
onUpdated: ({ form }) => {
if (form.valid) {
$message = "Form submitted successfully!";
}
}
});
</script>
<form method="POST" use:enhance>
<input
name="name"
bind:value={$form.name}
aria-invalid={$errors.name ? 'true' : undefined}
{...$constraints.name}
/>
{#if $errors.name}<span class="error">{$errors.name}</span>{/if}
<button type="submit" disabled={$submitting}>
{$submitting ? 'Submitting...' : 'Submit'}
</button>
</form>
{#if $message}<div class="message">{$message}</div>{/if}Comprehensive configuration options for customizing form behavior, validation, error handling, and user experience.
interface FormOptions<T, M, In = T> {
/** Unique identifier for the form instance */
id?: string;
/** Whether to apply SvelteKit's default form actions */
applyAction?: boolean | 'never';
/** When to invalidate page data after submission */
invalidateAll?: boolean | 'force' | 'pessimistic';
/** Whether to reset form after successful submission */
resetForm?: boolean | (() => boolean);
/** How to scroll to validation errors */
scrollToError?: 'auto' | 'smooth' | 'off' | boolean | ScrollIntoViewOptions;
/** Whether to auto-focus first error field */
autoFocusOnError?: boolean | 'detect';
/** CSS selector for error elements */
errorSelector?: string;
/** Whether to select error text when focusing */
selectErrorText?: boolean;
/** CSS selector for sticky navigation */
stickyNavbar?: string;
/** Message to show when leaving with unsaved changes */
taintedMessage?: string | boolean | null | ((nav: BeforeNavigate) => Promise<boolean>);
/** Enable single page application mode */
spa?: boolean | { failover?: boolean };
/** Data format for form submission */
dataType?: 'form' | 'json';
/** Client-side validation configuration */
validators?: ValidationAdapter<Partial<T>> | ClientValidationAdapter<Partial<T>> | false | 'clear';
/** Whether to use HTML5 custom validity */
customValidity?: boolean;
/** What to clear after form submission */
clearOnSubmit?: 'errors' | 'message' | 'errors-and-message' | 'none';
/** How to handle multiple simultaneous submissions */
multipleSubmits?: 'prevent' | 'allow' | 'abort';
/** Flash message integration */
flashMessage?: {
module: { getFlash(page: Page): FlashMessage };
onError?: ({ result, message }: { result: ActionResult; message: FlashMessage }) => void;
};
/** Event handlers */
onError?: (event: { result: ActionResult; message?: any }) => void;
onResult?: (event: { result: ActionResult; formEl: HTMLFormElement; cancel: () => void }) => void;
onSubmit?: (event: { formData: FormData; formElement: HTMLFormElement; controller: AbortController; cancel: () => void }) => void;
onUpdate?: (event: { form: SuperValidated<T, M>; formEl: HTMLFormElement; cancel: () => void }) => void;
onUpdated?: (event: { form: SuperValidated<T, M>; formEl: HTMLFormElement }) => void;
}Usage Examples:
const { form, enhance } = superForm(data.form, {
// Auto-reset after successful submission
resetForm: true,
// Smooth scroll to errors with auto-focus
scrollToError: 'smooth',
autoFocusOnError: true,
// Prevent leaving with unsaved changes
taintedMessage: "You have unsaved changes. Are you sure you want to leave?",
// Enable SPA mode with fallback
spa: { failover: true },
// Client-side validation
validators: zodClient(schema),
// Event handlers
onSubmit: ({ formData, cancel }) => {
// Pre-submission logic
if (!confirm('Submit form?')) {
cancel();
}
},
onUpdated: ({ form }) => {
if (form.valid) {
goto('/success');
}
},
onError: ({ result }) => {
console.error('Form submission failed:', result);
}
});Client-side validation capabilities with real-time feedback and comprehensive error handling.
/**
* Validates specific form fields or entire form
* @param path - Optional field path to validate (validates all if omitted)
* @returns Promise resolving to validation result
*/
validate(path?: FormPath<T>): Promise<boolean>;
/**
* Gets all validation errors with their field paths
* @returns Readable store containing errors grouped by field path
*/
allErrors: Readable<{ path: string; messages: string[] }[]>;
/**
* Resets form to initial state
* @param options - Reset configuration options
*/
reset(options?: { keepMessage?: boolean }): void;
/**
* Manually set validation errors on fields
* @param path - Field path to set error on
* @param error - Error message or array of messages
*/
setError(path: FormPath<T>, error: string | string[]): void;
/**
* Clear validation errors from specific fields
* @param path - Field path to clear errors from
*/
clearErrors(path?: FormPath<T>): void;Usage Examples:
<script lang="ts">
const { form, validate, allErrors, reset } = superForm(data.form);
// Validate specific field on blur
async function handleBlur(field: string) {
await validate(field);
}
// Validate entire form
async function handleValidate() {
const isValid = await validate();
if (!isValid) {
console.log('Validation errors:', $allErrors.map(e => `${e.path}: ${e.messages.join(', ')}`));
}
}
// Reset form
function handleReset() {
reset({ keepMessage: true });
}
</script>
<input
bind:value={$form.email}
on:blur={() => handleBlur('email')}
/>
<button type="button" on:click={handleValidate}>
Validate Form
</button>
<button type="button" on:click={handleReset}>
Reset Form
</button>Advanced form state management including snapshots, tainted field tracking, and form persistence.
/**
* Creates a snapshot of current form state
* @param options - Snapshot configuration
* @returns Form state snapshot
*/
snapshot(options?: { m?: boolean }): SuperFormSnapshot<T, M>;
/**
* Restores form state from a snapshot
* @param snapshot - Previously captured form snapshot
*/
restore(snapshot: SuperFormSnapshot<T, M>): void;
/**
* Captures current form data state
* @returns Lightweight form data snapshot
*/
capture(): FormSnapshot<T>;
/**
* Applies captured form data state
* @param snapshot - Previously captured form data
*/
apply(snapshot: FormSnapshot<T>): void;
interface SuperFormSnapshot<T, M> {
/** Form data at time of snapshot */
form: T;
/** Form message at time of snapshot */
message?: M;
/** Tainted fields at time of snapshot */
tainted?: TaintedFields<T>;
/** Form errors at time of snapshot */
errors?: ValidationErrors<T>;
/** Form ID */
id: string;
}
type FormSnapshot<T> = T;
interface TaintedFields<T> {
[K in keyof T]?: T[K] extends Record<string, unknown>
? TaintedFields<T[K]>
: T[K] extends Array<any>
? boolean[]
: boolean;
}Usage Examples:
<script lang="ts">
import { browser } from '$app/environment';
const { form, snapshot, restore } = superForm(data.form);
// Auto-save form state to localStorage
$: if (browser) {
localStorage.setItem('form-draft', JSON.stringify(snapshot()));
}
// Restore form state on page load
onMount(() => {
const saved = localStorage.getItem('form-draft');
if (saved) {
try {
const parsed = JSON.parse(saved);
restore(parsed);
} catch (e) {
console.warn('Failed to restore form state:', e);
}
}
});
// Clear saved state after successful submission
const handleSuccess = () => {
localStorage.removeItem('form-draft');
};
</script>Integration with SvelteKit's progressive enhancement system, providing full functionality without JavaScript while enhancing the experience when available.
/**
* SvelteKit enhance function for progressive enhancement
* Provides form submission handling that works with and without JavaScript
*/
enhance: SubmitFunction;
/**
* Enhanced form element binding that adds progressive enhancement
* @param node - Form element to enhance
* @param options - Enhancement options
* @returns Enhancement cleanup function
*/
use:enhance: Action<HTMLFormElement, FormOptions>;Usage Examples:
<script lang="ts">
const { form, errors, enhance, submitting } = superForm(data.form, {
// Form works without JS, enhanced with JS
applyAction: true,
invalidateAll: true,
// Enhanced behavior only with JS
scrollToError: 'smooth',
autoFocusOnError: true,
taintedMessage: 'You have unsaved changes'
});
</script>
<!-- This form works perfectly without JavaScript -->
<form method="POST" use:enhance>
<input name="name" bind:value={$form.name} required />
{#if $errors.name}<span class="error">{$errors.name}</span>{/if}
<input name="email" type="email" bind:value={$form.email} required />
{#if $errors.email}<span class="error">{$errors.email}</span>{/if}
<!-- Button state managed automatically -->
<button type="submit" disabled={$submitting}>
{$submitting ? 'Submitting...' : 'Submit'}
</button>
</form>interface SuperFormData<T> {
subscribe: Readable<T>['subscribe'];
set(this: void, value: T, options?: { taint?: TaintOption }): void;
update(this: void, updater: Updater<T>, options?: { taint?: TaintOption }): void;
}
interface SuperFormErrors<T> {
subscribe: Writable<ValidationErrors<T>>['subscribe'];
set(this: void, value: ValidationErrors<T>, options?: { force?: boolean }): void;
update(this: void, updater: Updater<ValidationErrors<T>>, options?: { force?: boolean }): void;
clear: () => void;
}
type ResetOptions<T> = {
keepMessage?: boolean;
data?: Partial<T>;
newState?: Partial<T>;
id?: string;
};
type ValidateOptions<Value, Out, In> = Partial<{
value: Value;
update: boolean | 'errors' | 'value';
taint: TaintOption;
errors: string | string[];
schema: ValidationAdapter<Out, In>;
}>;
type ChangeEvent<T> = {
path: FormPath<T>;
formElement: HTMLFormElement;
formData: FormData;
cancel(): void;
submitter?: HTMLElement;
};
type TaintOption = boolean | 'untaint' | 'untaint-all' | 'untaint-form';
type SuperFormValidated<T, M, In = T> = SuperValidated<T, M, In> & {
tainted: TaintedFields<T> | undefined
};Install with Tessl CLI
npx tessl i tessl/npm-sveltekit-superforms