Single-page applications (SPA)

It’s possible to use the whole Superforms library on the client, for example in single page applications or when you want to call an external API instead of the SvelteKit form actions. A SPA is easy to create with SvelteKit, documented here.

Usage

const { form, enhance } = superForm(data, {
  SPA: true
  validators: false | ClientValidationAdapter<S>
})

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 if the form is valid, and you can then use the onUpdate event as a submit handler, for example to call an external API with the form data.

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';
import { zod } from 'sveltekit-superforms/adapters';
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, zod(_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 now being used to validate the form data, instead of on the server:

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

<script lang="ts">
  import { superForm, setMessage, setError } from 'sveltekit-superforms';
  import { _userSchema } from './+page.js';
  import { zod } from 'sveltekit-superforms/adapters';

  let { data } = $props();

  const { form, errors, message, constraints, enhance } = superForm(
    data.form,
    {
      SPA: true,
      validators: zod(_userSchema),
      onUpdate({ form }) {
        // Form validation
        if (form.data.email.includes('spam')) {
          setError(form, 'email', 'Suspicious email address.');
        } else if (form.valid) {
          // TODO: Call an external API with form.data, await the result and update form
          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 the form parameter in onUpdate will update the form.

Without a +page.ts file

Since you can’t use top-level await in Svelte components, you can’t use superValidate directly in +page.svelte, as it is async. But if you want the default values only for the schema, you can import defaults to avoid having a +page.ts.

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

  const { form, errors, enhance } = superForm(defaults(zod(loginSchema)), {
    SPA: true,
    validators: zod(loginSchema),
    onUpdate({ form }) {
      if (form.valid) {
        // TODO: Call an external API with form.data, await the result and update form
      }
    }
  });
</script>

With initial top-level data

If you have initial data in the top level of the component, you can pass it as a first parameter to defaults, but remember that it won’t be validated. There’s a trick though; if you want to show errors for the initial data, you can call validateForm directly after superForm. The validators option must be set for this to work:

const initialData = { name: 'New user' };

const { form, errors, enhance, validateForm } = superForm(
  defaults(initialData, zod(loginSchema)), {
    SPA: true,
    validators: zod(loginSchema)
    // ...
  }
);

validateForm({ update: true });
Found a typo or an inconsistency? Make a quick correction here!