CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-sveltekit-superforms

Making SvelteKit forms a pleasure to use with comprehensive validation, type safety, and progressive enhancement.

Pending
Overview
Eval results
Files

client-management.mddocs/

Client-Side Management

Reactive form management with Svelte stores, real-time validation, error handling, progressive enhancement, and comprehensive form state management for SvelteKit applications.

Capabilities

SuperForm Function

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}

Form Configuration Options

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);
  }
});

Validation and Error Management

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>

Form State Management

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>

Progressive Enhancement

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>

Core Types

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

docs

client-management.md

field-proxies.md

index.md

server-processing.md

validation-adapters.md

tile.json