This is the documentation for Superforms version 1. The latest version can be found at superforms.rocks!

Single-page applications (SPA)

Even though validation has its given place on the server, it’s possible to use the whole Superforms library on the client in single page applications. A SPA is easy to create with SvelteKit, fully documented here.

Usage

const { form, enhance } = superForm(data, {
  SPA: true | { failStatus: number }
  validators: false | AnyZodObject | {
    field: (value) => string | string[] | null | undefined;
  }
})

By setting the SPA option to true, the form won’t be sent to the server when submitted. Instead, the client-side validators option will determine the success or failure of the form, which will trigger the event chain, and the validation result will be most conveniently consumed in the onUpdate event.

Using +page.ts instead of +page.server.ts

Since SPA pages don’t have a server representation, you can use +page.ts to load initial data. Combined with a route parameter, we can make a CRUD-like page in a straightforward manner:

src/routes/user/[id]/+page.ts

import { error } from '@sveltejs/kit';
import { superValidate } from 'sveltekit-superforms/client';
import { z } from 'zod';

export const _userSchema = z.object({
  id: z.number().int().positive(),
  name: z.string().min(2),
  email: z.string().email()
});

export const load = async ({ params, fetch }) => {
  const id = parseInt(params.id);

  const request = await fetch(
    `https://jsonplaceholder.typicode.com/users/${id}`
  );
  if (request.status >= 400) throw error(request.status);

  const userData = await request.json();
  const form = await superValidate(userData, _userSchema);

  return { form };
};

Displaying the form

We display the form in +page.svelte like before, but with the SPA option added, and the onUpdate event is used to validate the form data, instead of on the server:

src/routes/user/[id]/+page.svelte

<script lang="ts">
  import type { PageData } from './$types';
  import { superForm, setMessage, setError } from 'sveltekit-superforms/client';
  import { _userSchema } from './+page';

  export let data: PageData;

  const { form, errors, message, constraints, enhance } = superForm(
    data.form,
    {
      SPA: true,
      validators: _userSchema,
      onUpdate({ form }) {
        // Form validation
        if (form.data.email.includes('spam')) {
          setError(form, 'email', 'Suspicious email address.');
        } else if (form.valid) {
          // TODO: Do something with the validated form.data
          setMessage(form, 'Valid data!');
        }
      }
    }
  );
</script>

<h1>Edit user</h1>

{#if $message}<h3>{$message}</h3>{/if}

<form method="POST" use:enhance>
  <label>
    Name<br />
    <input
      aria-invalid={$errors.name ? 'true' : undefined}
      bind:value={$form.name}
      {...$constraints.name} />
  </label>
  {#if $errors.name}<span class="invalid">{$errors.name}</span>{/if}

  <label>
    E-mail<br />
    <input
      type="email"
      aria-invalid={$errors.email ? 'true' : undefined}
      bind:value={$form.email}
      {...$constraints.email} />
  </label>
  {#if $errors.email}<span class="invalid">{$errors.email}</span>{/if}

  <button>Submit</button>
</form>

The validation in onUpdate is almost the same as validating in a form action on the server. Nothing needs to be returned at the end since all modifications to form will reflect in the view after the onUpdate event is done.

Using superValidate in +page.svelte

Since you can’t use top-level await in Svelte components, you can’t use superValidate directly in +page.svelte, but you can import superValidateSync instead to avoid having a +page.ts if you just want the default values for a schema:

<script lang="ts">
  import { superForm, superValidateSync } from 'sveltekit-superforms/client';
  import { loginSchema } from '$lib/schemas';

  const { form, errors, enhance } = superForm(
    superValidateSync(loginSchema), {
      SPA: true,
      validators: loginSchema,
      onUpdate({ form }) {
        if (form.valid) {
          // TODO: Do something with the validated form.data
        }
      }
    }
  );
</script>

Test it out

The following form has SPA: true set, and is using +page.ts for loading the initial data. Take a look in the browser devtools and see that nothing is posted to the server on submit.

Trimming down the bundle size

In the above example, we’re adding both Zod and the Superforms server part to the client. This adds about 65 Kb to the client output size, which hopefully is negligible, but if you’re hunting bytes, you can optimize by skipping the superValidate part in +page.ts, construct your own SuperValidated object, and use the Superforms validators in +page.svelte:

src/lib/schemas.ts

import { z } from 'zod';

export const _userSchema = z.object({
  id: z.number().int().positive(),
  name: z.string().min(2),
  email: z.string().email()
});

export type UserSchema = typeof _userSchema;

src/routes/user/[id]/+page.ts

import type { SuperValidated } from 'sveltekit-superforms';
import type { UserSchema } from '$lib/schemas';
import { error } from '@sveltejs/kit';

export const load = async ({ params, fetch }) => {
  const id = parseInt(params.id);

  const request = await fetch(
    `https://jsonplaceholder.typicode.com/users/${id}`
  );
  if (request.status >= 400) throw error(request.status);

  const user = await request.json();

  // Validate data here if required

  const form : SuperValidated<UserSchema> = {
    valid: false, // Or true, depending on validation
    posted: false,
    data: user,
    errors: {},
    constraints: {}
  }

  return { form };
};

src/routes/user/[id]/+page.svelte

<script lang="ts">
  import type { PageData } from './$types';
  import { superForm } from 'sveltekit-superforms/client';

  export let data: PageData;

  const { form, errors, enhance } = superForm(data.form, {
    SPA: true,
    validators: {
      name: (name) => (name.length <= 2 ? 'Name is too short' : null),
      email: (email) =>
        email.includes('spam') ? 'Suspicious email address.' : null
    },
    onUpdate({ form }) {
      if (form.valid) {
        form.message = 'Valid data!';
      }
    }
  });
</script>

This will reduce the size to just the client-side part of Superforms, around 25 Kb. The downside is that you won’t get any constraints, the initial data won’t be validated, and you have to rely on the Superforms validator scheme, instead of the much more powerful Zod.