Multiple Select
Learn how to build multiple select inputs with Formsnap.
In the following guide, you'll learn how to setup and validate multiple select fields with Formsnap by building an Ice Cream order form.
Building a Multiple Select Form
Define the Schema
Here's the schema we'll use for the form we'll build in this guide. We're assuming you know how to setup the load function and actions, and have already created a +page.svelte
and +page.server.ts
file.
import { z } from "zod";
export const flavors = ["vanilla", "chocolate", "cookies and cream", "strawberry"] as const;
export const toppings = ["sprinkles", "hot fudge", "whipped cream", "cherry"] as const;
export const schema = z
.object({
scoops: z.number().min(1).default(1),
flavors: z.array(z.enum(flavors)).min(1, "You must select at least one flavor."),
toppings: z.array(z.enum(toppings)).max(2, "You can only select up to two toppings."),
})
.refine((data) => (data.flavors.length > data.scoops ? false : true), {
message: "You can only select as many flavors as you have scoops.",
path: ["flavors"],
});
The schema represents an ice cream order form with a scoops
field, a flavors
field, and a toppings
field. The flavors
and toppings
fields are arrays of enums, and we've added some custom validation to ensure the user can only select as many flavors as they have scoops. We've also set a minimum of 1 for the flavors
field and a maximum of 2 for the toppings
field.
Create the Form
Let's initialize our SuperForm with the form returned from the load
function and setup the basic structure of our form. We'll also want to import the schema
, flavors
, and toppings
from the schema file.
<script lang="ts">
import { superForm } from "sveltekit-superforms";
import { zodClient } from "sveltekit-superforms/adapters";
import { schema, flavors, toppings } from "./schema.js";
let { data } = $props();
const form = superForm(data.form, {
validators: zodClient(schema),
});
const { form: formData } = form;
</script>
<form use:form.enhance method="POST">
<!-- ... -->
<button type="submit">Submit</button>
</form>
Import the Components
At a minimum we need to import the Field, Control, Label, and FieldErrors components from Formsnap.
<script lang="ts">
import { superForm } from "sveltekit-superforms";
import { zodClient } from "sveltekit-superforms/adapters";
import { Field, Control, Label, FieldErrors } from "formsnap";
import { schema } from "./schema.js";
let { data } = $props();
const form = superForm(data.form, {
validators: zodClient(schema),
});
const { form: formData } = form;
</script>
<form use:form.enhance method="POST">
<!-- ... -->
<button type="submit">Submit</button>
</form>
Create the Scoops Field
The first field we'll create is the scoops
field, which will be a regular select input with a range of 1 to 5 scoops.
<!-- script tag -->
<form use:form.enhance method="POST">
<Field {form} name="scoops">
<Control>
{#snippet children({ props })}
<div class="flex flex-col items-start gap-1">
<Label>Number of scoops</Label>
<select {...props} bind:value={$formData.scoops}>
{#each Array.from({ length: 5 }, (_, i) => i + 1) as num}
<option value={num}>
{num === 1 ? `${num} Scoop` : `${num} Scoops`}
</option>
{/each}
</select>
</div>
{/snippet}
</Control>
<FieldErrors />
</Field>
<button type="submit">Submit</button>
</form>
Create the Flavors Field
Next, let's create the flavors
field. This field will be a multiple select input with the available flavors as options.
<!-- script tag -->
<form use:form.enhance method="POST">
<Field {form} name="scoops">
<Control>
{#snippet children({ props })}
<div class="flex flex-col items-start gap-1">
<Label>Number of scoops</Label>
<select {...props} bind:value={$formData.scoops}>
{#each Array.from({ length: 5 }, (_, i) => i + 1) as num}
<option value={num}>
{num === 1 ? `${num} Scoop` : `${num} Scoops`}
</option>
{/each}
</select>
</div>
{/snippet}
</Control>
<FieldErrors />
</Field>
<Field {form} name="flavors">
<Control>
{#snippet children({ props })}
<div class="flex flex-col items-start gap-1">
<Label>What flavors do you fancy?</Label>
<select multiple bind:value={$formData.flavors} {...props}>
{#each flavors as flavor}
<option value={flavor} selected={$formData.flavors.includes(flavor)}>
{flavor}
</option>
{/each}
</select>
</div>
{/snippet}
</Control>
<FieldErrors />
</Field>
<button type="submit">Submit</button>
</form>
Notice that we're using the multiple
attribute on the select
element to allow the user to select multiple options. We're also using the selected
attribute to pre-select the options that are already in the formData.flavors
array.
Create the Toppings Field
Finally, let's create the toppings
field. This field will also be a multiple select input with the available toppings as options.
<!-- script tag -->
<form use:form.enhance method="POST">
<Field {form} name="scoops">
<Control>
{#snippet children({ props })}
<div class="flex flex-col items-start gap-1">
<Label>Number of scoops</Label>
<select {...props} bind:value={$formData.scoops}>
{#each Array.from({ length: 5 }, (_, i) => i + 1) as num}
<option value={num}>
{num === 1 ? `${num} Scoop` : `${num} Scoops`}
</option>
{/each}
</select>
</div>
{/snippet}
</Control>
<FieldErrors />
</Field>
<Field {form} name="flavors">
<Control>
{#snippet children({ props })}
<div class="flex flex-col items-start gap-1">
<Label>What flavors do you fancy?</Label>
<select multiple bind:value={$formData.flavors} {...props}>
{#each flavors as flavor}
<option value={flavor} selected={$formData.flavors.includes(flavor)}>
{flavor}
</option>
{/each}
</select>
</div>
{/snippet}
</Control>
<FieldErrors />
</Field>
<Field {form} name="toppings">
<Control>
{#snippet children({ props })}
<div class="flex flex-col items-start gap-1">
<Label>Select your toppings</Label>
<select multiple bind:value={$formData.toppings} {...props}>
{#each toppings as topping}
<option value={topping} selected={$formData.toppings.includes(topping)}>
{topping}
</option>
{/each}
</select>
</div>
{/snippet}
</Control>
<FieldErrors />
</Field>
<button type="submit">Submit</button>
</form>
Finished Product
That's it! 🎉
You've created the functionality for a form containing multiple select inputs with validation. With some custom styles and finesse, you can make the form look something like this: