Docs
Formsnap & Superforms

Formsnap & Superforms

Building forms with Formsnap, Superforms, & Zod.

Forms are tricky. They are one of the most common things you'll build in a web application, but also one of the most complex.

Well-designed HTML forms are:

  • Well-structured and semantically correct.
  • Easy to use and navigate (keyboard).
  • Accessible with ARIA attributes and proper labels.
  • Has support for client and server side validation.
  • Well-styled and consistent with the rest of the application.

In this guide, we will take a look at building forms with formsnap, sveltekit-superforms and zod.

Features

The Form components offered by shadcn-svelte are wrappers around formsnap & sveltekit-superforms which provide a few things:

  • Composable components for building forms.
  • A <Form.Field /> component for building controlled form fields.
  • Form validation using zod.
  • Applies the correct aria attributes to form fields based on states.
  • Enables you to easily use various components like Select, RadioGroup, Switch, Checkbox and other form components as form fields.
  • Provides an optional native <select /> & <input type="radio" /> with out of the box functionality if you prefer to use native form elements rather than the bits-ui components.

If you aren't familiar with Superforms & Formsnap, you should check out their documentation first, as this guide assumes you have a basic understanding of how they work together.

Anatomy

	<Form.Root>
  <Form.Field>
    <Form.Item>
      <Form.Label />
      <!-- Any Form input component -->
      <Form.Description />
      <Form.Validation />
    </Form.Item>
  </Form.Field>
</Form.Root>

Example

	<Form.Root {schema} {form} let:config>
  <Form.Field {config} name="email">
    <Form.Item>
      <Form.Label />
      <Form.Input />
      <Form.Description />
      <Form.Validation />
    </Form.Item>
  </Form.Field>
</Form.Root>

Installation

	npx shadcn-svelte@latest add form

Usage

Create a form schema

Define the shape of your form using a Zod schema. You can read more about using Zod in the Zod documentation. We're going to define it in a file called schema.ts in the same directory as our page component, but you can put it anywhere you like.

src/routes/settings/schema.ts
	import { z } from "zod";

export const formSchema = z.object({
  username: z.string().min(2).max(50),
});

export type FormSchema = typeof formSchema;

Return the form from the route's load function

src/routes/settings/+page.server.ts
	import type { PageServerLoad } from "./$types";
import { superValidate } from "sveltekit-superforms/server";
import { formSchema } from "./schema";

export const load: PageServerLoad = async () => {
  return {
    form: await superValidate(formSchema),
  };
};

Create a form component

For this example, we'll be passing the form returned from the load function as a prop to this component. To ensure it's typed properly, we'll use the SuperValidated type from sveltekit-superforms, and pass in the type of our form schema.

src/routes/settings/settings-form.svelte
	<script lang="ts">
  import * as Form from "$lib/components/ui/form";
  import { formSchema, type FormSchema } from "./schema";
  import type { SuperValidated } from "sveltekit-superforms";

  export let form: SuperValidated<FormSchema>;
</script>

<Form.Root method="POST" {form} schema={formSchema} let:config>
  <Form.Field {config} name="username">
    <Form.Item>
      <Form.Label>Username</Form.Label>
      <Form.Input />
      <Form.Description>This is your public display name.</Form.Description>
      <Form.Validation />
    </Form.Item>
  </Form.Field>
  <Form.Button>Submit</Form.Button>
</Form.Root>

The name, value, and all accessibility attributes will be automatically applied to the input thanks to Formsnap.

Create a page component that uses the form

We'll pass the form from the data returned from the load function to the form component we created above.

src/routes/settings/+page.svelte
	<script lang="ts">
  import type { PageData } from "./$types";
  import SettingsForm from "./settings-form.svelte";
  export let data: PageData;
</script>

<SettingsForm form={data.form} />

Create an Action that handles the form submission

src/routes/settings/+page.server.ts
	import type { PageServerLoad, Actions } from "./$types";
import { fail } from "@sveltejs/kit";
import { superValidate } from "sveltekit-superforms/server";
import { formSchema } from "./schema";

export const load: PageServerLoad = async () => {
  return {
    form: await superValidate(formSchema),
  };
};

export const actions: Actions = {
  default: async (event) => {
    const form = await superValidate(event, formSchema);
    if (!form.valid) {
      return fail(400, {
        form,
      });
    }
    return {
      form,
    };
  },
};

Done

That's it. You now have a fully accessible form that is type-safe and has client & server side validation.

This is your public display name.

Options

You can handle the form submission in a few different ways using the options from formsnap. See the documentation for more information.

Click the above Submit button to see the different options in action.

Examples

See the following links for more examples on how to use the other Form components: