` element would accept along with a few additional props:\n\n\n\nA `$bindable` reference to the underlying HTML element rendered by the `Legend` component.\n\n \n\n\n\nIf provided, the `Legend` component will not render an HTML element and will instead expect you to spread the snippet's `props` onto an element of your choosing.\n\nSee the [`child`](/docs/composition/child) snippet documentation for more information.\n\n \n\n\" name=\"...rest\">\n\nAny additional props provided to the `Legend` component will be spread onto the underlying HTML element.\n\n \n\n## Data Attributes\n\nThe following attributes are automatically applied to the element rendered by the `Legend` component.\n\n\n\nApplied to the element for selection during styling or otherwise.\n\n \n\n\n\nApplied to the element when a validation error exists on the field.\n\n ",
- "toc": [
- {
- "title": "Props",
- "url": "#props",
- "items": []
- },
- {
- "title": "Data Attributes",
- "url": "#data-attributes",
- "items": []
- }
- ],
- "section": "Components",
- "slug": "components/legend",
- "slugFull": "/components/legend"
- },
- {
- "title": "Bits UI Select",
- "description": "How to use the Select component from Bits UI with Formsnap.",
- "path": "recipes/bits-ui-select",
- "content": "\nThe Select
component from Bits UI is a simple, yet powerful component for building a custom select menu. It powers the Select
component for shadcn-svelte , which is one of the most popular UI projects for Svelte. This recipe will demonstrate how to integrate that component with Formsnap.
\nSingle Select \nWe're going to build a \"languages\" select menu that allows the user to select a single language from a list of pre-defined options. We'll use a code to represent the language's value, and the language's name as the label.
\n\nDefine the Schema
\nHere's the schema we'll use for the form we'll build in this guide. We'll assume you know how to setup the load function and actions in the +page.server.ts
file.
\nimport { z } from \"zod\";\n\nexport const languages = {\n\ten: \"English\",\n\tes: \"Spanish\",\n\tfr: \"French\",\n\tde: \"German\",\n\tit: \"Italian\",\n\tpt: \"Portuguese\",\n\tru: \"Russian\",\n\tzh: \"Chinese\",\n\tja: \"Japanese\",\n\tko: \"Korean\",\n} as const;\n\ntype Language = keyof typeof languages;\n\nexport const schema = z.object({\n\tlanguage: z.enum(Object.keys(languages) as [Language, ...Language[]]).default(\"en\"),\n});\n
\nSetup the Form
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { Select } from \"bits-ui\";\n\timport { Field, Control, Label, FieldErrors } from \"formsnap\";\n\timport { schema, languages } from \"./schema.js\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\n\tconst { form: formData, enhance } = form;\n\n\tconst selectedLanguageLabel = $derived(\n\t\t$formData.language ? languages[$formData.language] : \"Select a language\"\n\t);\n</script>\n\n<form method=\"POST\" use:enhance>\n\t<Field {form} name=\"language\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<Label>Language</Label>\n\t\t\t\t<Select.Root type=\"single\" bind:value={$formData.language} name={props.name}>\n\t\t\t\t\t<Select.Trigger {...props}>\n\t\t\t\t\t\t{selectedLabel}\n\t\t\t\t\t</Select.Trigger>\n\t\t\t\t\t<Select.Content>\n\t\t\t\t\t\t{#each Object.entries(languages) as [value, label]}\n\t\t\t\t\t\t\t<Select.Item {value}>\n\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t</Select.Item>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t</Select.Content>\n\t\t\t\t</Select.Root>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<Description>The docs will be translated to your preferred language.</Description>\n\t\t<FieldErrors />\n\t</Field>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nWe apply the control props
to the Select.Trigger
component so that the label and other accessibility attributes are associated with it.
\nWe apply the props.name
to the Select.Root
component so a hidden input is rendered for the select.
\nFinished Product
\nThat's it! 🎉
\nWith some additional styles and structure, the form could look something like this:
\n\n \nMultiple Select \nThe <Select />
component also supports multiple selection. Here's how you can use it to build a multi-select form.
\n\nDefine the Schema
\nHere's the schema we'll use for the form we'll build in this guide. We'll assume you know how to setup the load function and actions in the +page.server.ts
file.
\nimport { z } from \"zod\";\n\nexport const colors = {\n\tblu: \"Blue\",\n\tred: \"Red\",\n\tgrn: \"Green\",\n\tylw: \"Yellow\",\n\tblk: \"Black\",\n} as const;\n\ntype Color = keyof typeof colors;\n\nexport const schema = z.object({\n\tcolors: z\n\t\t.array(z.enum(Object.keys(colors) as [Color, ...Color[]]))\n\t\t.min(1, \"Please select at least one color.\"),\n});\n
\nSetup the Form
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { Select } from \"bits-ui\";\n\timport { Field, Control, Label, FieldErrors } from \"formsnap\";\n\timport { schema, colors } from \"./schema.js\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\n\tconst { form: formData } = form;\n\n\tconst selectedColors = $derived(\n\t\t$formData.colors.length ? $formData.colors.map((c) => colors[c]).join(\",\") : \"Select colors\"\n\t);\n</script>\n\n<form method=\"POST\" use:form.enhance>\n\t<Field {form} name=\"colors\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<Label>Favorite colors</Label>\n\t\t\t\t<Select.Root type=\"multiple\" bind:value={$formData.colors} name={props.name}>\n\t\t\t\t\t<Select.Trigger {...props}>\n\t\t\t\t\t\t{selectedColors}\n\t\t\t\t\t</Select.Trigger>\n\t\t\t\t\t<Select.Content>\n\t\t\t\t\t\t{#each Object.entries(colors) as [value, label]}\n\t\t\t\t\t\t\t<Select.Item {value} {label} />\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t</Select.Content>\n\t\t\t\t</Select.Root>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<Description>We'll use these colors to customize your experience.</Description>\n\t\t<FieldErrors />\n\t</Field>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nWe apply the control props
to the Select.Trigger
component so that the label and other accessibility attributes are associated with it.
\nWe apply the props.name
to the Select.Root
component so a hidden input is rendered for the select.
\nFinished Product
\nThat's it! 🎉
\nWith some additional styles and structure, the form could look something like this:
\n\n ",
- "raw": "\n\nThe `Select` component from [Bits UI](https://bits-ui.com/docs/components/select) is a simple, yet powerful component for building a custom select menu. It powers the `Select` component for [shadcn-svelte](https://shadcn-svelte.com/docs/components/select), which is one of the most popular UI projects for Svelte. This recipe will demonstrate how to integrate that component with Formsnap.\n\n## Single Select\n\nWe're going to build a \"languages\" select menu that allows the user to select a single language from a list of pre-defined options. We'll use a code to represent the language's value, and the language's name as the label.\n\n\n\nDefine the Schema \n\nHere's the schema we'll use for the form we'll build in this guide. We'll assume you know how to setup the load function and actions in the `+page.server.ts` file.\n\n```ts title=\"schema.ts\"\nimport { z } from \"zod\";\n\nexport const languages = {\n\ten: \"English\",\n\tes: \"Spanish\",\n\tfr: \"French\",\n\tde: \"German\",\n\tit: \"Italian\",\n\tpt: \"Portuguese\",\n\tru: \"Russian\",\n\tzh: \"Chinese\",\n\tja: \"Japanese\",\n\tko: \"Korean\",\n} as const;\n\ntype Language = keyof typeof languages;\n\nexport const schema = z.object({\n\tlanguage: z.enum(Object.keys(languages) as [Language, ...Language[]]).default(\"en\"),\n});\n```\n\nSetup the Form \n\n```svelte title=\"+page.svelte\"\n\n\n\n```\n\nWe apply the control `props` to the `Select.Trigger` component so that the label and other accessibility attributes are associated with it.\n\nWe apply the `props.name` to the `Select.Root` component so a hidden input is rendered for the select.\n\nFinished Product \n\nThat's it! 🎉\n\nWith some additional styles and structure, the form could look something like this:\n\n \n\n \n\n## Multiple Select\n\nThe ` ` component also supports multiple selection. Here's how you can use it to build a multi-select form.\n\n\n\nDefine the Schema \n\nHere's the schema we'll use for the form we'll build in this guide. We'll assume you know how to setup the load function and actions in the `+page.server.ts` file.\n\n```ts title=\"schema.ts\"\nimport { z } from \"zod\";\n\nexport const colors = {\n\tblu: \"Blue\",\n\tred: \"Red\",\n\tgrn: \"Green\",\n\tylw: \"Yellow\",\n\tblk: \"Black\",\n} as const;\n\ntype Color = keyof typeof colors;\n\nexport const schema = z.object({\n\tcolors: z\n\t\t.array(z.enum(Object.keys(colors) as [Color, ...Color[]]))\n\t\t.min(1, \"Please select at least one color.\"),\n});\n```\n\nSetup the Form \n\n```svelte title=\"+page.svelte\"\n\n\n\n```\n\nWe apply the control `props` to the `Select.Trigger` component so that the label and other accessibility attributes are associated with it.\n\nWe apply the `props.name` to the `Select.Root` component so a hidden input is rendered for the select.\n\nFinished Product \n\nThat's it! 🎉\n\nWith some additional styles and structure, the form could look something like this:\n\n \n\n ",
- "toc": [
- {
- "title": "Single Select",
- "url": "#single-select",
- "items": []
- },
- {
- "title": "Multiple Select",
- "url": "#multiple-select",
- "items": []
- }
- ],
- "section": "Recipes",
- "slug": "recipes/bits-ui-select",
- "slugFull": "/recipes/bits-ui-select"
- },
- {
- "title": "Checkbox Groups",
- "description": "Learn how to build checkbox group inputs with Formsnap.",
- "path": "recipes/checkbox-groups",
- "content": "\nCheckbox groups are a set of checkboxes that allow users to select multiple options from a list, and are quite common in forms.
\nIn this guide, you'll learn how to build a checkbox group with Formsnap by building an \"Allergies\" checkbox group, where a user must select any food allergies they have. We'll start with very basic functionality and then look at more advanced refinements for validation.
\nCreate a Checkbox Group \nFor the purposes of this guide, we'll assume you're using the zod
and zodClient
adapters from Superforms , but the same principles apply to all adapters.
\n\nDefine the Schema
\nLet's start by defining a schema that contains an array
to hold the selected options. We'll create this inside the context=\"module\"
script tag of our Svelte component so we can access it in our component and +page.server.ts
file.
\n<script lang=\"ts\" context=\"module\">\n\timport { z } from \"zod\";\n\n\tconst allergies = [\"None\", \"Peanuts\", \"Shellfish\", \"Lactose\", \"Gluten\"] as const;\n\n\texport const schema = z.object({\n\t\tallergies: z\n\t\t\t.array(z.enum(allergies))\n\t\t\t.min(1, \"If you don't have any allergies, select 'None'.\"),\n\t});\n</script>\n
\nWe've defined an array named allergies
that holds the possible enum values, and then created a schema that requires at least one option to be selected.
\nSetup the Load Function & Actions
\nNext, we'll create a +page.server.ts
file where we'll define our load
function and actions
to handle the form submission.
\nimport { superValidate } from \"sveltekit-superforms\";\nimport type { Actions, PageServerLoad } from \"./$types\";\nimport { schema } from \"./+page.svelte\";\nimport { zod } from \"sveltekit-superforms/adapters\";\nimport { fail } from \"@sveltejs/kit\";\n\nexport const load: PageServerLoad = async () => {\n\treturn {\n\t\tform: await superValidate(zod(schema)),\n\t};\n};\n\nexport const actions: Actions = {\n\tdefault: async (event) => {\n\t\tconst form = await superValidate(event, zod(schema));\n\n\t\tif (!form.valid) {\n\t\t\treturn fail(400, { form });\n\t\t}\n\n\t\treturn { form };\n\t},\n};\n
\nNotice we're importing that schema we defined in our +page.svelte
file and using it to validate the form data in the load
function and actions
.
\nInitialize the SuperForm
\nNow that we have our schema defined and our load
function and actions
set up, we can initialize the SuperForm in our Svelte component.
\n<!-- script context=\"module\" tag -->\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport SuperDebug from \"sveltekit-superforms\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\tconst { form: formData } = form;\n</script>\n
\nWe're using the superForm
function to initialize the form, passing in the form
object from our load
function and the zodClient
adapter to handle client-side validation.
\nImport Components and Enhance the Form
\nNow that our SuperForm is initialized, we can use it to construct our checkbox group.
\nWe'll first import the components we'll need from Formsnap, and then setup a form
element with the enhance
action to progressively enhance the form with client-side validation.
\n<!-- script context=\"module\" tag -->\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { Fieldset, Legend, Label, Control, FieldErrors, Description } from \"formsnap\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\tconst { form: formData } = form;\n</script>\n\n<form method=\"POST\" use:form.enhance>\n\t<!-- ... -->\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nConstruct the Fieldset
\nSince each checkbox in the group is related to a single field, we'll use a Fieldset
component with a Legend
to group them together. We'll use the Description
component to provide more context about the fieldset and the FieldErrors
component to display validation errors.
\n<!-- script tags -->\n<form method=\"POST\" use:form.enhance>\n\t<Fieldset {form} name=\"allergies\">\n\t\t<Legend>Select your allergies</Legend>\n\t\t<!-- ... -->\n\t\t<Description>We'll accommodate your dietary restrictions.</Description>\n\t\t<FieldErrors />\n\t</Fieldset>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nNext, we'll iterate over the allergies
array and create a Control that includes a Label and a checkbox input for each option.
\n<!-- script tags -->\n<form method=\"POST\" use:form.enhance>\n\t<Fieldset {form} name=\"allergies\">\n\t\t<Legend>Select your allergies</Legend>\n\t\t{#each allergies as allergy}\n\t\t\t<Control>\n\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t<input\n\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t{...props}\n\t\t\t\t\t\tbind:group={$formData.allergies}\n\t\t\t\t\t\tvalue={allergy}\n\t\t\t\t\t/>\n\t\t\t\t\t<Label>{value}</Label>\n\t\t\t\t{/snippet}\n\t\t\t</Control>\n\t\t{/each}\n\t\t<Description>We'll accommodate your dietary restrictions.</Description>\n\t\t<FieldErrors />\n\t</Fieldset>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nImprove Validation
\nWe now have a functional checkbox group that allows users to select multiple options from a list. However, we can make some improvements to enhance the user experience and provide better validation feedback.
\nYou may have noticed that users can select \"None\"
and another allergy at the same time, which doesn't make sense. We can address this by adding a refinement to our schema to ensure that if \"None\"
is selected, no other allergies can be selected.
\n<script lang=\"ts\" context=\"module\">\n\timport { z } from \"zod\";\n\n\tconst allergies = [\"None\", \"Peanuts\", \"Shellfish\", \"Lactose\", \"Gluten\"] as const;\n\n\texport const schema = z.object({\n\t\tallergies: z\n\t\t\t.array(z.enum(allergies))\n\t\t\t.min(1, \"If you don't have any allergies, select 'None'.\")\n\t\t\t.refine((v) => {\n\t\t\t\treturn v.includes(\"None\") ? v.length === 1 : true;\n\t\t\t}, \"If you select 'None', you can't select any other allergies.\"),\n\t});\n</script>\n\n<!-- ...rest -->\n
\nWe've added a refine
method to the allergies
array to ensure that if \"None\"
is selected, no other allergies can be selected. If the user selects \"None\"
, the array length must be 1
, otherwise the validation will fail and the custom error message will be displayed.
\nFinished Product
\nThat's it! You've successfully created a checkbox group with Formsnap. With some custom styles and components applied, the finished product might look something like this:
\n\n \nTLDR - Show Me the Code \nFor those who prefer to skip the guide and get straight to the code, here's the code required to create a checkbox group with Formsnap.
\nimport { superValidate } from \"sveltekit-superforms\";\nimport type { Actions, PageServerLoad } from \"./$types\";\nimport { schema } from \"./+page.svelte\";\nimport { zod } from \"sveltekit-superforms/adapters\";\nimport { fail } from \"@sveltejs/kit\";\n\nexport const load: PageServerLoad = async () => {\n\treturn {\n\t\tform: await superValidate(zod(schema)),\n\t};\n};\n\nexport const actions: Actions = {\n\tdefault: async (event) => {\n\t\tconst form = await superValidate(event, zod(schema));\n\n\t\tif (!form.valid) {\n\t\t\treturn fail(400, { form });\n\t\t}\n\n\t\treturn { form };\n\t},\n};\n
\n<script lang=\"ts\" context=\"module\">\n\timport { z } from \"zod\";\n\n\texport const allergies = [\"None\", \"Peanuts\", \"Shellfish\", \"Lactose\", \"Gluten\"] as const;\n\n\texport const schema = z.object({\n\t\tallergies: z\n\t\t\t.array(z.enum(allergies))\n\t\t\t.min(1, \"If you don't have any allergies, select 'None'.\")\n\t\t\t.refine((v) => {\n\t\t\t\treturn v.includes(\"None\") ? v.length === 1 : true;\n\t\t\t}, \"If you select 'None', you can't select any other allergies.\"),\n\t});\n</script>\n\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { Fieldset, Legend, Label, Control, FieldErrors, Description } from \"formsnap\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data, {\n\t\tvalidators: zodClient(schema),\n\t});\n\tconst { form: formData } = form;\n</script>\n\n<form method=\"POST\" use:form.enhance>\n\t<Fieldset {form} name=\"allergies\">\n\t\t<Legend>Select any allergies you may have</Legend>\n\t\t{#each allergies as allergy}\n\t\t\t<Control>\n\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t<input\n\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t{...props}\n\t\t\t\t\t\tbind:group={$formData.allergies}\n\t\t\t\t\t\tvalue={allergy}\n\t\t\t\t\t/>\n\t\t\t\t\t<Label>{allergy}</Label>\n\t\t\t\t{/snippet}\n\t\t\t</Control>\n\t\t{/each}\n\t\t<Description>We'll accommodate your dietary restrictions.</Description>\n\t\t<FieldErrors />\n\t</Fieldset>\n\t<button type=\"submit\">Submit</button>\n</form>\n
",
- "raw": "\n\nCheckbox groups are a set of checkboxes that allow users to select multiple options from a list, and are quite common in forms.\n\nIn this guide, you'll learn how to build a checkbox group with Formsnap by building an \"Allergies\" checkbox group, where a user must select any food allergies they have. We'll start with very basic functionality and then look at more advanced refinements for validation.\n\n## Create a Checkbox Group\n\nFor the purposes of this guide, we'll assume you're using the `zod` and `zodClient` adapters from [Superforms](https://superforms.rocks), but the same principles apply to all adapters.\n\n\n\nDefine the Schema \n\nLet's start by defining a schema that contains an `array` to hold the selected options. We'll create this inside the `context=\"module\"` script tag of our Svelte component so we can access it in our component and `+page.server.ts` file.\n\n```svelte title=\"+page.svelte\"\n\n```\n\nWe've defined an array named `allergies` that holds the possible enum values, and then created a schema that requires at least one option to be selected.\n\nSetup the Load Function & Actions \n\nNext, we'll create a `+page.server.ts` file where we'll define our `load` function and `actions` to handle the form submission.\n\n```ts title=\"+page.server.ts\"\nimport { superValidate } from \"sveltekit-superforms\";\nimport type { Actions, PageServerLoad } from \"./$types\";\nimport { schema } from \"./+page.svelte\";\nimport { zod } from \"sveltekit-superforms/adapters\";\nimport { fail } from \"@sveltejs/kit\";\n\nexport const load: PageServerLoad = async () => {\n\treturn {\n\t\tform: await superValidate(zod(schema)),\n\t};\n};\n\nexport const actions: Actions = {\n\tdefault: async (event) => {\n\t\tconst form = await superValidate(event, zod(schema));\n\n\t\tif (!form.valid) {\n\t\t\treturn fail(400, { form });\n\t\t}\n\n\t\treturn { form };\n\t},\n};\n```\n\nNotice we're importing that schema we defined in our `+page.svelte` file and using it to validate the form data in the `load` function and `actions`.\n\nInitialize the SuperForm \n\nNow that we have our schema defined and our `load` function and `actions` set up, we can initialize the SuperForm in our Svelte component.\n\n```svelte title=\"+page.svelte\"\n\n\n```\n\nWe're using the `superForm` function to initialize the form, passing in the `form` object from our `load` function and the `zodClient` adapter to handle client-side validation.\n\nImport Components and Enhance the Form \n\nNow that our SuperForm is initialized, we can use it to construct our checkbox group.\n\nWe'll first import the components we'll need from Formsnap, and then setup a `form` element with the `enhance` action to progressively enhance the form with client-side validation.\n\n```svelte title=\"+page.svelte\" {5,15-18}\n\n\n\n\n```\n\nConstruct the Fieldset \n\nSince each checkbox in the group is related to a single field, we'll use a `Fieldset` component with a `Legend` to group them together. We'll use the `Description` component to provide more context about the fieldset and the `FieldErrors` component to display validation errors.\n\n```svelte {3-8}\n\n\n```\n\nNext, we'll iterate over the `allergies` array and create a [Control](/docs/components/control) that includes a [Label](/docs/components/label) and a checkbox input for each option.\n\n```svelte {5-17}\n\n\n```\n\nImprove Validation \n\nWe now have a functional checkbox group that allows users to select multiple options from a list. However, we can make some improvements to enhance the user experience and provide better validation feedback.\n\nYou may have noticed that users can select `\"None\"` and another allergy at the same time, which doesn't make sense. We can address this by adding a refinement to our schema to ensure that if `\"None\"` is selected, no other allergies can be selected.\n\n```svelte title=\"+page.svelte\" {10-12}\n\n\n\n```\n\nWe've added a `refine` method to the `allergies` array to ensure that if `\"None\"` is selected, no other allergies can be selected. If the user selects `\"None\"`, the array length must be `1`, otherwise the validation will fail and the custom error message will be displayed.\n\nFinished Product \n\nThat's it! You've successfully created a checkbox group with Formsnap. With some custom styles and components applied, the finished product might look something like this:\n\n \n\n \n\n## TLDR - Show Me the Code\n\nFor those who prefer to skip the guide and get straight to the code, here's the code required to create a checkbox group with Formsnap.\n\n```ts title=\"+page.server.ts\"\nimport { superValidate } from \"sveltekit-superforms\";\nimport type { Actions, PageServerLoad } from \"./$types\";\nimport { schema } from \"./+page.svelte\";\nimport { zod } from \"sveltekit-superforms/adapters\";\nimport { fail } from \"@sveltejs/kit\";\n\nexport const load: PageServerLoad = async () => {\n\treturn {\n\t\tform: await superValidate(zod(schema)),\n\t};\n};\n\nexport const actions: Actions = {\n\tdefault: async (event) => {\n\t\tconst form = await superValidate(event, zod(schema));\n\n\t\tif (!form.valid) {\n\t\t\treturn fail(400, { form });\n\t\t}\n\n\t\treturn { form };\n\t},\n};\n```\n\n```svelte title=\"+page.svelte\"\n\n\n\n\n\n```",
- "toc": [
- {
- "title": "Create a Checkbox Group",
- "url": "#create-a-checkbox-group",
- "items": []
- },
- {
- "title": "TLDR - Show Me the Code",
- "url": "#tldr---show-me-the-code",
- "items": []
- }
- ],
- "section": "Recipes",
- "slug": "recipes/checkbox-groups",
- "slugFull": "/recipes/checkbox-groups"
- },
- {
- "title": "Dynamic Fields",
- "description": "Learn how to creating dynamic fields by building a URLs field with Formsnap.",
- "path": "recipes/dynamic-fields",
- "content": "\nTo create a dynamic field, you'll need to use the ElementField component, that allows you to treat each element of an array as it's own field.
\nIn this recipe, we'll create a URLs field where users can add and remove URLs from their profile.
\nCreate Dynamic Fields \n\nDefine the Schema
\nHere's the schema we'll use for the form we'll build in this guide. We'll assume you know how to setup the load function and actions in the +page.server.ts
file.
\nimport { z } from \"zod\";\n\nexport const schema = z.object({\n\turls: z\n\t\t.array(z.string().url({ message: \"Please enter a valid URL.\" }))\n\t\t.min(2, \"You must include at least two URLs on your profile.\")\n\t\t.default([\"\", \"\"]),\n});\n
\nWe've defined an array named urls
that contains strings that must be valid URLs. We've also set a minimum length of 2 for the array itself, and provided two default values to start with. The minimum length of 2 may sounds strange, but we're only doing so to demonstrate different validation errors for the array and its elements.
\nCreate the Form
\nWe'll need to initialize our SuperForm with the form returned from the load
function, and then setup the basic structure of our form.
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { schema } from \"./schema.js\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\n\tconst { form: formData } = form;\n</script>\n\n<form use:form.enhance method=\"POST\">\n\t<!-- ... -->\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nImport the Components
\nWe have a few components we need to import to build the form.
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport {\n\t\tFieldset,\n\t\tLegend,\n\t\tElementField,\n\t\tControl,\n\t\tLabel,\n\t\tFieldErrors,\n\t\tDescription,\n\t} from \"formsnap\";\n\timport { schema } from \"./schema.js\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\n\tconst { form: formData } = form;\n</script>\n\n<form use:form.enhance method=\"POST\">\n\t<!-- ... -->\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nScaffold the Fieldset
\nSince our individual URL inputs will be part of the same field, we'll use a Fieldset component to group them together and a Legend to provide a title.
\n<!-- script tag -->\n<form use:form.enhance method=\"POST\">\n\t<Fieldset {form} name=\"urls\">\n\t\t<Legend>Public URLs</Legend>\n\t\t<!-- ... -->\n\t\t<Description>Add URLs to your profile that you'd like to share with others.</Description>\n\t\t<FieldErrors />\n\t</Fieldset>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nThe FieldErrors component will display any validation errors for the array itself. In our case, it will display an error if the array doesn't contain at least two URLs (we'll add the errors for the individual URLs in the next step).
\nThe Description component will provide additional context about the fields once we've created them, but each field will share the same description from the Fieldset scope.
\nRender the URL Fields
\nNow that we've scaffolded the Fieldset
, we can iterate over the $formData.urls
array to render the individual URL fields, which are represented by the ElementField component.
\n<!-- script tag -->\n<form use:enhance method=\"POST\">\n\t<Fieldset {form} name=\"urls\">\n\t\t<Legend>Public URLs</Legend>\n\t\t{#each $formData.urls as _, i}\n\t\t\t<ElementField {form} name=\"urls[{i}]\">\n\t\t\t\t<Control>\n\t\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t\t<Label class=\"sr-only\">URL {i + 1}</Label>\n\t\t\t\t\t\t<input type=\"url\" {...props} bind:value={$formData.urls[i]} />\n\t\t\t\t\t{/snippet}\n\t\t\t\t</Control>\n\t\t\t\t<Description class=\"sr-only\">\n\t\t\t\t\tThis URL will be publicly available on your profile.\n\t\t\t\t</Description>\n\t\t\t\t<FieldErrors />\n\t\t\t</ElementField>\n\t\t{/each}\n\t\t<FieldErrors />\n\t</Fieldset>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nWe're using the ElementField
component to treat each element of the urls
array as a separate field with its own state and validation. We're also using the Control
component to create a label and input for each URL, and binding the input's value to the corresponding element of the urls
array.
\n\nYou should always include a label for each input for accessibility purposes. In this case, because we don't want to display a label visually for each input, we've added a class to the label to visually hide it while still making it available to screen readers.
\n \nMake the Fields Dynamic
\nAt the moment, the user can only have two URLs in their profile. We want to allow them to add and remove URLs as needed. We can achieve this by adding buttons to add and remove URLs.
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport {\n\t\tFieldset,\n\t\tLegend,\n\t\tElementField,\n\t\tControl,\n\t\tLabel,\n\t\tFieldErrors,\n\t\tDescription,\n\t} from \"formsnap\";\n\timport { schema } from \"./schema.js\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\n\tconst { form: formData } = form;\n\n\tfunction removeUrlByIndex(index: number) {\n\t\t$formData.urls = $formData.urls.filter((_, i) => i !== index);\n\t}\n\n\tfunction addUrl() {\n\t\t$formData.urls = [...$formData.urls, \"\"];\n\t}\n</script>\n\n<form use:form.enhance method=\"POST\">\n\t<Fieldset {form} name=\"urls\">\n\t\t<Legend>Public URLs</Legend>\n\t\t{#each $formData.urls as _, i}\n\t\t\t<ElementField {form} name=\"urls[{i}]\">\n\t\t\t\t<Control>\n\t\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t\t<Label class=\"sr-only\">URL {i + 1}</Label>\n\t\t\t\t\t\t<input type=\"url\" {...props} bind:value={$formData.urls[i]} />\n\t\t\t\t\t\t<button type=\"button\" onclick={() => removeUrlByIndex(i)}>\n\t\t\t\t\t\t\tRemove URL\n\t\t\t\t\t\t</button>\n\t\t\t\t\t{/snippet}\n\t\t\t\t</Control>\n\t\t\t\t<Description class=\"sr-only\">\n\t\t\t\t\tThis URL will be publicly available on your profile.\n\t\t\t\t</Description>\n\t\t\t\t<FieldErrors />\n\t\t\t</ElementField>\n\t\t{/each}\n\t\t<FieldErrors />\n\t\t<button type=\"button\" onclick={addUrl}>Add URL</button>\n\t</Fieldset>\n\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nWe've added a removeUrlByIndex
function that removes a URL from the urls
array by its index, and a addUrl
function that adds a new URL to the urls
array. We've also added a button to remove each URL and a button to add a new URL.
\nNow the user can add and remove URLs as needed, and the form will validate the array and its elements according to the schema we defined.
\nFinished Product
\nThat's it! 🎉
\nYou've created a dynamic field that allows users to add and remove URLs from their profile. With some custom styles and finesse, you can make the form look something like this:
\n\n \nTLDR - Show Me the Code \nHere's the complete code for the form we built in this guide:
\nimport { z } from \"zod\";\n\nexport const schema = z.object({\n\turls: z\n\t\t.array(z.string().url({ message: \"Please enter a valid URL.\" }))\n\t\t.min(2, \"You must include at least two URLs on your profile.\")\n\t\t.default([\"\", \"\"]),\n});\n
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport {\n\t\tFieldset,\n\t\tLegend,\n\t\tElementField,\n\t\tControl,\n\t\tLabel,\n\t\tFieldErrors,\n\t\tDescription,\n\t} from \"formsnap\";\n\timport { schema } from \"./schema.js\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\n\tconst { form: formData } = form;\n\n\tfunction removeUrlByIndex(index: number) {\n\t\t$formData.urls = $formData.urls.filter((_, i) => i !== index);\n\t}\n\n\tfunction addUrl() {\n\t\t$formData.urls = [...$formData.urls, \"\"];\n\t}\n</script>\n\n<form use:form.enhance method=\"POST\">\n\t<Fieldset {form} name=\"urls\">\n\t\t<Legend>Public URLs</Legend>\n\t\t{#each $formData.urls as _, i}\n\t\t\t<ElementField {form} name=\"urls[{i}]\">\n\t\t\t\t<Control>\n\t\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t\t<Label class=\"sr-only\">URL {i + 1}</Label>\n\t\t\t\t\t\t<input type=\"url\" {...props} bind:value={$formData.urls[i]} />\n\t\t\t\t\t\t<button type=\"button\" onclick={() => removeUrlByIndex(i)}>\n\t\t\t\t\t\t\tRemove URL\n\t\t\t\t\t\t</button>\n\t\t\t\t\t{/snippet}\n\t\t\t\t</Control>\n\t\t\t\t<Description class=\"sr-only\">\n\t\t\t\t\tThis URL will be publicly available on your profile.\n\t\t\t\t</Description>\n\t\t\t</ElementField>\n\t\t{/each}\n\t\t<FieldErrors />\n\t\t<button type=\"button\" onclick={addUrl}>Add URL</button>\n\t</Fieldset>\n\n\t<button type=\"submit\">Submit</button>\n</form>\n
",
- "raw": "\n\nTo create a dynamic field, you'll need to use the [ElementField](/docs/components/element-field) component, that allows you to treat each element of an array as it's own field.\n\nIn this recipe, we'll create a URLs field where users can add and remove URLs from their profile.\n\n## Create Dynamic Fields\n\n\n\nDefine the Schema \n\nHere's the schema we'll use for the form we'll build in this guide. We'll assume you know how to setup the load function and actions in the `+page.server.ts` file.\n\n```ts title=\"schema.ts\"\nimport { z } from \"zod\";\n\nexport const schema = z.object({\n\turls: z\n\t\t.array(z.string().url({ message: \"Please enter a valid URL.\" }))\n\t\t.min(2, \"You must include at least two URLs on your profile.\")\n\t\t.default([\"\", \"\"]),\n});\n```\n\nWe've defined an array named `urls` that contains strings that must be valid URLs. We've also set a minimum length of 2 for the array itself, and provided two default values to start with. The minimum length of 2 may sounds strange, but we're only doing so to demonstrate different validation errors for the array and its elements.\n\nCreate the Form \n\nWe'll need to initialize our SuperForm with the form returned from the `load` function, and then setup the basic structure of our form.\n\n```svelte title=\"+page.svelte\"\n\n\n\n```\n\nImport the Components \n\nWe have a few components we need to import to build the form.\n\n```svelte title=\"+page.svelte\" {4-12}\n\n\n\n```\n\nScaffold the Fieldset \n\nSince our individual URL inputs will be part of the same field, we'll use a [Fieldset](/docs/components/fieldset) component to group them together and a [Legend](/docs/components/legend) to provide a title.\n\n```svelte title=\"+page.svelte\" {3-8}\n\n\n```\n\nThe [FieldErrors](/docs/components/field-errors) component will display any validation errors for the array itself. In our case, it will display an error if the array doesn't contain at least two URLs (we'll add the errors for the individual URLs in the next step).\n\nThe [Description](/docs/components/description) component will provide additional context about the fields once we've created them, but each field will share the same description from the [Fieldset](/docs/components/fieldset) scope.\n\nRender the URL Fields \n\nNow that we've scaffolded the `Fieldset`, we can iterate over the `$formData.urls` array to render the individual URL fields, which are represented by the [ElementField](/docs/components/element-field) component.\n\n```svelte title=\"+page.svelte\" {5-18}\n\n\n```\n\nWe're using the `ElementField` component to treat each element of the `urls` array as a separate field with its own state and validation. We're also using the `Control` component to create a label and input for each URL, and binding the input's value to the corresponding element of the `urls` array.\n\n\n\nYou should always include a label for each input for accessibility purposes. In this case, because we don't want to display a label visually for each input, we've added a class to the label to visually hide it while still making it available to screen readers.\n\n \n\nMake the Fields Dynamic \n\nAt the moment, the user can only have two URLs in their profile. We want to allow them to add and remove URLs as needed. We can achieve this by adding buttons to add and remove URLs.\n\n```svelte showLineNumbers title=\"+page.svelte\" {23-29,41-43,53}\n\n\n\n```\n\nWe've added a `removeUrlByIndex` function that removes a URL from the `urls` array by its index, and a `addUrl` function that adds a new URL to the `urls` array. We've also added a button to remove each URL and a button to add a new URL.\n\nNow the user can add and remove URLs as needed, and the form will validate the array and its elements according to the schema we defined.\n\nFinished Product \n\nThat's it! 🎉\n\nYou've created a dynamic field that allows users to add and remove URLs from their profile. With some custom styles and finesse, you can make the form look something like this:\n\n \n\n \n\n## TLDR - Show Me the Code\n\nHere's the complete code for the form we built in this guide:\n\n```ts title=\"schema.ts\"\nimport { z } from \"zod\";\n\nexport const schema = z.object({\n\turls: z\n\t\t.array(z.string().url({ message: \"Please enter a valid URL.\" }))\n\t\t.min(2, \"You must include at least two URLs on your profile.\")\n\t\t.default([\"\", \"\"]),\n});\n```\n\n```svelte title=\"+page.svelte\"\n\n\n\n```",
- "toc": [
- {
- "title": "Create Dynamic Fields",
- "url": "#create-dynamic-fields",
- "items": []
- },
- {
- "title": "TLDR - Show Me the Code",
- "url": "#tldr---show-me-the-code",
- "items": []
- }
- ],
- "section": "Recipes",
- "slug": "recipes/dynamic-fields",
- "slugFull": "/recipes/dynamic-fields"
- },
- {
- "title": "Multiple Select",
- "description": "Learn how to build multiple select inputs with Formsnap.",
- "path": "recipes/multiple-select",
- "content": "\nIn the following guide, you'll learn how to setup and validate multiple select fields with Formsnap by building an Ice Cream order form.
\nBuilding a Multiple Select Form \n\nDefine the Schema
\nHere'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.
\nimport { z } from \"zod\";\n\nexport const flavors = [\"vanilla\", \"chocolate\", \"cookies and cream\", \"strawberry\"] as const;\n\nexport const toppings = [\"sprinkles\", \"hot fudge\", \"whipped cream\", \"cherry\"] as const;\n\nexport const schema = z\n\t.object({\n\t\tscoops: z.number().min(1).default(1),\n\t\tflavors: z.array(z.enum(flavors)).min(1, \"You must select at least one flavor.\"),\n\t\ttoppings: z.array(z.enum(toppings)).max(2, \"You can only select up to two toppings.\"),\n\t})\n\t.refine((data) => (data.flavors.length > data.scoops ? false : true), {\n\t\tmessage: \"You can only select as many flavors as you have scoops.\",\n\t\tpath: [\"flavors\"],\n\t});\n
\nThe 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.
\nCreate the Form
\nLet'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.
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { schema, flavors, toppings } from \"./schema.js\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\n\tconst { form: formData } = form;\n</script>\n\n<form use:form.enhance method=\"POST\">\n\t<!-- ... -->\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nImport the Components
\nAt a minimum we need to import the Field , Control , Label , and FieldErrors components from Formsnap.
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { Field, Control, Label, FieldErrors } from \"formsnap\";\n\timport { schema } from \"./schema.js\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\n\tconst { form: formData } = form;\n</script>\n\n<form use:form.enhance method=\"POST\">\n\t<!-- ... -->\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nCreate the Scoops Field
\nThe first field we'll create is the scoops
field, which will be a regular select input with a range of 1 to 5 scoops.
\n<!-- script tag -->\n<form use:form.enhance method=\"POST\">\n\t<Field {form} name=\"scoops\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<div class=\"flex flex-col items-start gap-1\">\n\t\t\t\t\t<Label>Number of scoops</Label>\n\t\t\t\t\t<select {...props} bind:value={$formData.scoops}>\n\t\t\t\t\t\t{#each Array.from({ length: 5 }, (_, i) => i + 1) as num}\n\t\t\t\t\t\t\t<option value={num}>\n\t\t\t\t\t\t\t\t{num === 1 ? `${num} Scoop` : `${num} Scoops`}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<FieldErrors />\n\t</Field>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nCreate the Flavors Field
\nNext, let's create the flavors
field. This field will be a multiple select input with the available flavors as options.
\n<!-- script tag -->\n<form use:form.enhance method=\"POST\">\n\t<Field {form} name=\"scoops\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<div class=\"flex flex-col items-start gap-1\">\n\t\t\t\t\t<Label>Number of scoops</Label>\n\t\t\t\t\t<select {...props} bind:value={$formData.scoops}>\n\t\t\t\t\t\t{#each Array.from({ length: 5 }, (_, i) => i + 1) as num}\n\t\t\t\t\t\t\t<option value={num}>\n\t\t\t\t\t\t\t\t{num === 1 ? `${num} Scoop` : `${num} Scoops`}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<FieldErrors />\n\t</Field>\n\t<Field {form} name=\"flavors\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<div class=\"flex flex-col items-start gap-1\">\n\t\t\t\t\t<Label>What flavors do you fancy?</Label>\n\t\t\t\t\t<select multiple bind:value={$formData.flavors} {...props}>\n\t\t\t\t\t\t{#each flavors as flavor}\n\t\t\t\t\t\t\t<option value={flavor} selected={$formData.flavors.includes(flavor)}>\n\t\t\t\t\t\t\t\t{flavor}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<FieldErrors />\n\t</Field>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nNotice 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.
\nCreate the Toppings Field
\nFinally, let's create the toppings
field. This field will also be a multiple select input with the available toppings as options.
\n<!-- script tag -->\n<form use:form.enhance method=\"POST\">\n\t<Field {form} name=\"scoops\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<div class=\"flex flex-col items-start gap-1\">\n\t\t\t\t\t<Label>Number of scoops</Label>\n\t\t\t\t\t<select {...props} bind:value={$formData.scoops}>\n\t\t\t\t\t\t{#each Array.from({ length: 5 }, (_, i) => i + 1) as num}\n\t\t\t\t\t\t\t<option value={num}>\n\t\t\t\t\t\t\t\t{num === 1 ? `${num} Scoop` : `${num} Scoops`}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<FieldErrors />\n\t</Field>\n\t<Field {form} name=\"flavors\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<div class=\"flex flex-col items-start gap-1\">\n\t\t\t\t\t<Label>What flavors do you fancy?</Label>\n\t\t\t\t\t<select multiple bind:value={$formData.flavors} {...props}>\n\t\t\t\t\t\t{#each flavors as flavor}\n\t\t\t\t\t\t\t<option value={flavor} selected={$formData.flavors.includes(flavor)}>\n\t\t\t\t\t\t\t\t{flavor}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<FieldErrors />\n\t</Field>\n\t<Field {form} name=\"toppings\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<div class=\"flex flex-col items-start gap-1\">\n\t\t\t\t\t<Label>Select your toppings</Label>\n\t\t\t\t\t<select multiple bind:value={$formData.toppings} {...props}>\n\t\t\t\t\t\t{#each toppings as topping}\n\t\t\t\t\t\t\t<option value={topping} selected={$formData.toppings.includes(topping)}>\n\t\t\t\t\t\t\t\t{topping}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<FieldErrors />\n\t</Field>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nFinished Product
\nThat's it! 🎉
\nYou'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:
\n\n ",
- "raw": "\n\nIn the following guide, you'll learn how to setup and validate multiple select fields with Formsnap by building an Ice Cream order form.\n\n## Building a Multiple Select Form\n\n\n\nDefine the Schema \n\nHere'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.\n\n```ts title=\"schema.ts\"\nimport { z } from \"zod\";\n\nexport const flavors = [\"vanilla\", \"chocolate\", \"cookies and cream\", \"strawberry\"] as const;\n\nexport const toppings = [\"sprinkles\", \"hot fudge\", \"whipped cream\", \"cherry\"] as const;\n\nexport const schema = z\n\t.object({\n\t\tscoops: z.number().min(1).default(1),\n\t\tflavors: z.array(z.enum(flavors)).min(1, \"You must select at least one flavor.\"),\n\t\ttoppings: z.array(z.enum(toppings)).max(2, \"You can only select up to two toppings.\"),\n\t})\n\t.refine((data) => (data.flavors.length > data.scoops ? false : true), {\n\t\tmessage: \"You can only select as many flavors as you have scoops.\",\n\t\tpath: [\"flavors\"],\n\t});\n```\n\nThe 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.\n\nCreate the Form \n\nLet'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.\n\n```svelte title=\"+page.svelte\"\n\n\n\n```\n\nImport the Components \n\nAt a minimum we need to import the [Field](/docs/components/field), [Control](/docs/components/control), [Label](/docs/components/label), and [FieldErrors](/docs/components/field-errors) components from Formsnap.\n\n```svelte title=\"+page.svelte\" {4}\n\n\n\n```\n\nCreate the Scoops Field \n\nThe first field we'll create is the `scoops` field, which will be a regular select input with a range of 1 to 5 scoops.\n\n```svelte title=\"+page.svelte\" {3-19}\n\n\n```\n\nCreate the Flavors Field \n\nNext, let's create the `flavors` field. This field will be a multiple select input with the available flavors as options.\n\n```svelte title=\"+page.svelte\" {20-36}\n\n\n```\n\nNotice 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.\n\nCreate the Toppings Field \n\nFinally, let's create the `toppings` field. This field will also be a multiple select input with the available toppings as options.\n\n```svelte title=\"+page.svelte\" {37-53}\n\n\n```\n\nFinished Product \n\nThat's it! 🎉\n\nYou'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:\n\n \n\n ",
- "toc": [
- {
- "title": "Building a Multiple Select Form",
- "url": "#building-a-multiple-select-form",
- "items": []
- }
- ],
- "section": "Recipes",
- "slug": "recipes/multiple-select",
- "slugFull": "/recipes/multiple-select"
- }
-]
\ No newline at end of file
+ {
+ "title": "Introduction",
+ "description": "What is this?",
+ "path": "index",
+ "content": "Formsnap takes the already incredible sveltekit-superforms (winner of Svelte Hack 2023 for best library), made by the brilliant Andreas Söderlund and wraps it with components that make it simpler to use while making your forms accessible by default.
\nThe Same Form, Two Ways \nTo showcase the value provided by Formsnap, let's take a look at a simple sign up form using only Superforms, and then using Superforms with Formsnap.
\nSuperforms Only \n<script lang=\"ts\">\n\timport type { PageData } from \"./$types\";\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { signupFormSchema } from \"./schema\";\n\tlet { data } = $props();\n\n\tconst { form, errors, enhance, constraints } = superForm(data.form, {\n\t\tvalidators: zodClient(signupFormSchema),\n\t});\n</script>\n\n<form method=\"POST\" use:enhance>\n\t<label for=\"name\">Name</label>\n\t<input\n\t\tid=\"name\"\n\t\tname=\"name\"\n\t\taria-describedby={$errors.name ? \"name-error name-desc\" : \"name-desc\"}\n\t\taria-invalid={$errors.name ? \"true\" : undefined}\n\t\taria-required={$constraints.name?.required ? \"true\" : undefined}\n\t\tbind:value={$form.name}\n\t/>\n\t<span id=\"name-desc\">Be sure to use your real name.</span>\n\t<span id=\"name-error\" aria-live=\"assertive\">\n\t\t{#if $errors.name.length}\n\t\t\t{#each $errors.name as err}\n\t\t\t\t{err}\n\t\t\t{/each}\n\t\t{/if}\n\t</span>\n\t<label for=\"email\">Email</label>\n\t<input\n\t\tid=\"email\"\n\t\tname=\"email\"\n\t\ttype=\"email\"\n\t\taria-describedby={$errors.email ? \"email-error email-desc\" : \"email-desc\"}\n\t\taria-invalid={$errors.email ? \"true\" : undefined}\n\t\taria-required={$constraints.email?.required ? \"true\" : undefined}\n\t\tbind:value={$form.email}\n\t/>\n\t<span id=\"email-desc\">It's preferred that you use your company email.</span>\n\t<span id=\"email-error\" aria-live=\"assertive\">\n\t\t{#if $errors.email.length}\n\t\t\t{#each $errors.email as err}\n\t\t\t\t{err}\n\t\t\t{/each}\n\t\t{/if}\n\t</span>\n\t<label for=\"password\">Password</label>\n\t<input\n\t\tid=\"password\"\n\t\tname=\"password\"\n\t\ttype=\"password\"\n\t\taria-describedby={$errors.password ? \"password-error password-desc\" : \"password-desc\"}\n\t\taria-invalid={$errors.password ? \"true\" : undefined}\n\t\taria-required={$constraints.password?.required ? \"true\" : undefined}\n\t\tbind:value={$form.password}\n\t/>\n\t<span id=\"password-desc\">Ensure the password is at least 10 characters.</span>\n\t<span id=\"password-error\" aria-live=\"assertive\">\n\t\t{#if $errors.password.length}\n\t\t\t{#each $errors.password as err}\n\t\t\t\t{err}\n\t\t\t{/each}\n\t\t{/if}\n\t</span>\n\t<button>Submit</button>\n</form>\n
\nThat's quite a bit of code required to get a simple, accessible form up and running. We can't move as quickly as we'd like to, it's not very DRY, and is ripe for copy-paste errors.
\nAll is not lost though, as the whole idea behind Formsnap is to make this process simpler, without sacrificing the flexibility that Superforms provides.
\nSuperforms + Formsnap \n<script lang=\"ts\">\n\timport { Field, Control, Label, FieldErrors, Description } from \"formsnap\";\n\timport { signupFormSchema } from \"./schema.ts\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { superForm } from \"sveltekit-superforms\";\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(signupFormSchema),\n\t});\n\tconst { form: formData, enhance } = form;\n</script>\n\n<form method=\"POST\" use:enhance>\n\t<Field {form} name=\"name\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<Label>Name</Label>\n\t\t\t\t<input {...props} bind:value={$formData.name} />\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<Description>Be sure to use your real name.</Description>\n\t\t<FieldErrors />\n\t</Field>\n\t<Field {form} name=\"email\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<Label>Email</Label>\n\t\t\t\t<input {...props} type=\"email\" bind:value={$formData.email} />\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<Description>It's preferred that you use your company email.</Description>\n\t\t<FieldErrors />\n\t</Field>\n\t<Field {form} name=\"password\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<Label>Password</Label>\n\t\t\t\t<input {...props} type=\"password\" bind:value={$formData.password} />\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<Description>Ensure the password is at least 10 characters.</Description>\n\t\t<FieldErrors />\n\t</Field>\n</form>\n
\nThat's it! We just condensed a bunch of code, while retaining the same functionality.
\nNext Steps \nTo get started using Formsnap, head over to the Quick start section of the docs, where you'll learn how to install and use the library.
",
+ "raw": "Formsnap takes the already incredible [sveltekit-superforms](https://github.com/ciscoheat/sveltekit-superforms) (winner of [Svelte Hack 2023](https://hack.sveltesociety.dev/winners) for best library), made by the brilliant [Andreas Söderlund](https://github.com/ciscoheat) and wraps it with components that make it simpler to use while making your forms accessible by default.\n\n## The Same Form, Two Ways\n\nTo showcase the value provided by Formsnap, let's take a look at a simple sign up form using only Superforms, and then using Superforms with Formsnap.\n\n### Superforms Only\n\n```svelte title=\"+page.svelte\"\n\n\n\n```\n\nThat's quite a bit of code required to get a simple, accessible form up and running. We can't move as quickly as we'd like to, it's not very DRY, and is ripe for copy-paste errors.\n\nAll is not lost though, as the whole idea behind Formsnap is to make this process simpler, without sacrificing the flexibility that Superforms provides.\n\n### Superforms + Formsnap\n\n```svelte title=\"+page.svelte\"\n\n\n\n```\n\nThat's it! We just condensed a bunch of code, while retaining the same functionality.\n\n## Next Steps\n\nTo get started using Formsnap, head over to the [Quick start](/docs/quick-start) section of the docs, where you'll learn how to install and use the library.",
+ "toc": [
+ {
+ "title": "The Same Form, Two Ways",
+ "url": "#the-same-form-two-ways",
+ "items": [
+ {
+ "title": "Superforms Only",
+ "url": "#superforms-only",
+ "items": []
+ },
+ {
+ "title": "Superforms + Formsnap",
+ "url": "#superforms--formsnap",
+ "items": []
+ }
+ ]
+ },
+ {
+ "title": "Next Steps",
+ "url": "#next-steps",
+ "items": []
+ }
+ ],
+ "section": "Anchors",
+ "slug": "index",
+ "slugFull": "/index"
+ },
+ {
+ "title": "Quick start",
+ "description": "Learn how to take off with Formsnap by building a settings form.",
+ "path": "quick-start",
+ "content": "\nInstallation \nSince Formsnap is built on top of Superforms , you'll need to install it as well as a schema validation library of your choice. We'll use Zod .
\nnpm install formsnap sveltekit-superforms zod\n
\nTutorial: Build a settings form \nBefore diving into this tutorial, it's important to be confident with Superforms , as Formsnap is built on top of it and uses the same APIs.
\n\nDefine a Zod schema
\nThis schema will represent the shape of our form data. It's used to validate the form data on the client (optional) and server, along with some other useful things.
\nimport { z } from \"zod\";\n\nexport const themes = [\"light\", \"dark\"] as const;\nexport const languages = [\"en\", \"es\", \"fr\"] as const;\nexport const allergies = [\"peanuts\", \"dairy\", \"gluten\", \"soy\", \"shellfish\"] as const;\n\nexport const schema = z.object({\n\temail: z.string().email(\"Please enter a valid email.\"),\n\tbio: z.string().optional(),\n\ttheme: z.enum(themes).default(\"light\"),\n\tlanguage: z.enum(languages).default(\"en\"),\n\tmarketingEmails: z.boolean().default(true),\n\tallergies: z.array(z.enum(allergies)),\n});\n
\nLooking at the schema above, we know we'll need a few different input types to represent the different data types. Here's how we'll map the schema to input types:
\n\nemail
-> <input type=\"email\">
\nbio
-> <textarea>
\ntheme
-> <input type=\"radio\">
\nlanguage
-> <select>
\nmarketingEmails
-> <input type=\"checkbox>
\nallergies
-> <input type=\"checkbox\">
(group/multiple) \n \nOf course, there are other ways to represent the data, but this is the approach we'll take for this tutorial.
\nReturn the form from a load function
\nIn Superforms fashion, we'll return the form from a load function to seamlessly merge our PageData
and ActionData
.
\nimport type { PageServerLoad } from \"./$types\";\nimport { schema } from \"./schema\";\nimport { superValidate } from \"sveltekit-superforms\";\nimport { zod } from \"sveltekit-superforms/adapters\";\n\nexport const load: PageServerLoad = async () => {\n\treturn {\n\t\tform: await superValidate(zod(schema)),\n\t};\n};\n
\nSetup the form in the page component
\nNow that we have our form in the PageData
object, we can use it, along with the schema we defined earlier, to setup the form in our page component.
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { Field } from \"formsnap\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { allergies, schema, themes } from \"./schema.js\";\n\timport SuperDebug from \"sveltekit-superforms\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\tconst { form: formData, enhance } = form;\n</script>\n\n<form method=\"POST\" use:enhance>\n\t<!-- ... -->\n</form>\n<SuperDebug data={$formData} />\n
\nWe'll initialize the super form using superForm
and pass in the form from the PageData
. We'll also enable client-side validation by passing the validators
option. Then, we'll setup the form using the enhance
function, which will progressively enhance the form with client-side validation and other features.
\nConstructing a form field
\nYou can think of form fields as the building blocks of your form. Each property of the schema will have a corresponding form field, which will be responsible for displaying the error messages and description.
\nWe'll start with the email
field and work our way down.
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { allergies, schema, themes } from \"./schema.js\";\n\timport SuperDebug from \"sveltekit-superforms\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\tconst { form: formData, enhance } = form;\n</script>\n\n<form method=\"POST\" use:enhance>\n\t<Field {form} name=\"email\">\n\t\t<!-- ... -->\n\t</Field>\n</form>\n<SuperDebug data={$formData} />\n
\nWe pass the form
and name
to the Field
component, which will be used to setup the context for the field. The name
is typed to the keys of the schema, so it's type-safe.
\nNow let's add the remaining parts of the field:
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { Field, Control, Label, Description, FieldErrors } from \"formsnap\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { allergies, schema, themes } from \"./schema.js\";\n\timport SuperDebug from \"sveltekit-superforms\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\tconst { form: formData, enhance } = form;\n</script>\n\n<form method=\"POST\" use:enhance>\n\t<Field {form} name=\"email\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<Label>Email</Label>\n\t\t\t\t<input {...props} type=\"email\" bind:value={$formData.email} />\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<Description>Use your company email if you have one.</Description>\n\t\t<FieldErrors />\n\t</Field>\n</form>\n<SuperDebug data={$formData} />\n
\nWe've first added the Control component. Control
s are used to represent a form control and its label. They keep the control and label in sync via the props
snippet prop, which is spread onto the control. Inside the Control
, we've added the Label component, which will automatically associate itself with the control the props
are spread onto. We've also added the control itself, which is an input
that we're binding to the email
property of the form data.
\nThe Description component is optional, but it's useful for providing additional context to the user about the field. It'll be synced with the aria-describedby
attribute on the input, so it's accessible to screen readers.
\nThe FieldErrors component is used to display validation errors to the user. It also is synced with the aria-describedby
attribute on the input, which can receive multiple IDs, so that screen readers are able to read the error messages in addition to the description.
\nAnd that's really all it takes to setup a form field. Let's continue on with the rest of the fields.
\nAdd remaining form fields
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { Field, Control, Label, Description, FieldErrors, Fieldset, Legend } from \"formsnap\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { allergies, schema, themes } from \"./schema.js\";\n\timport SuperDebug from \"sveltekit-superforms\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\tconst { form: formData, enhance } = form;\n</script>\n\n<form use:enhance class=\"mx-auto flex max-w-md flex-col\" method=\"POST\">\n\t<Field {form} name=\"email\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<Label>Email</Label>\n\t\t\t\t<input {...props} type=\"email\" bind:value={$formData.email} />\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<Description>Company email is preferred</Description>\n\t\t<FieldErrors />\n\t</Field>\n\t<Field {form} name=\"bio\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<Label>Bio</Label>\n\t\t\t\t<textarea {...props} bind:value={$formData.bio} />\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<Description>Tell us a bit about yourself.</Description>\n\t\t<FieldErrors />\n\t</Field>\n\t<Field {form} name=\"language\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<Label>Language</Label>\n\t\t\t\t<select {...props} bind:value={$formData.language}>\n\t\t\t\t\t<option value=\"fr\">French</option>\n\t\t\t\t\t<option value=\"es\">Spanish</option>\n\t\t\t\t\t<option value=\"en\">English</option>\n\t\t\t\t</select>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<Description>Help us address you properly.</Description>\n\t\t<FieldErrors />\n\t</Field>\n\t<Fieldset {form} name=\"theme\">\n\t\t<Legend>Select your theme</Legend>\n\t\t{#each themes as theme}\n\t\t\t<Control>\n\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t<Label>{theme}</Label>\n\t\t\t\t\t<input {...props} type=\"radio\" value={theme} bind:group={$formData.theme} />\n\t\t\t\t{/snippet}\n\t\t\t</Control>\n\t\t{/each}\n\t\t<Description>We prefer dark mode, but the choice is yours.</Description>\n\t\t<FieldErrors />\n\t</Fieldset>\n\t<Field {form} name=\"marketingEmails\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<input {...props} type=\"checkbox\" bind:checked={$formData.marketingEmails} />\n\t\t\t\t<Label>I want to receive marketing emails</Label>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<Description>Stay up to date with our latest news and offers.</Description>\n\t\t<FieldErrors />\n\t</Field>\n\t<Fieldset {form} name=\"allergies\">\n\t\t<Legend>Food allergies</Legend>\n\t\t{#each allergies as allergy}\n\t\t\t<Control>\n\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t<input\n\t\t\t\t\t\t{...props}\n\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\tbind:group={$formData.allergies}\n\t\t\t\t\t\tvalue={allergy}\n\t\t\t\t\t/>\n\t\t\t\t\t<Label>{allergy}</Label>\n\t\t\t\t{/snippet}\n\t\t\t</Control>\n\t\t{/each}\n\t\t<Description>When we provide lunch, we'll accommodate your needs.</Description>\n\t\t<FieldErrors />\n\t</Fieldset>\n\t<button>Submit</button>\n</form>\n<SuperDebug data={$formData} />\n
\nYou may have noticed for the allergies
and theme
fields, we used the Fieldset and Legend components. These are used to group related fields together and provide a title for the group, which is great for accessibility and organization. Additionally, we only use a single FieldError and Description component for the entire group, and use an Control for each field in the group to associate the label with the control.
\n \nAnd that's it! You've now successfully built a settings form with Formsnap!
\nNext Steps \nNow that you've built your first form, you're ready to start building more complex forms with Formsnap & Superforms. Be sure to check out the rest of the documentation to learn more about the different components and APIs available to you.
",
+ "raw": "\n\n## Installation\n\nSince Formsnap is built on top of [Superforms](https://superforms.rocks), you'll need to install it as well as a schema validation library of your choice. We'll use [Zod](https://zod.dev).\n\n```bash\nnpm install formsnap sveltekit-superforms zod\n```\n\n## Tutorial: Build a settings form\n\nBefore diving into this tutorial, it's important to be confident with [Superforms](https://superforms.rocks), as Formsnap is built on top of it and uses the same APIs.\n\n\n\nDefine a Zod schema \n\nThis schema will represent the shape of our form data. It's used to validate the form data on the client (optional) and server, along with some other useful things.\n\n```ts title=\"src/routes/settings/schema.ts\"\nimport { z } from \"zod\";\n\nexport const themes = [\"light\", \"dark\"] as const;\nexport const languages = [\"en\", \"es\", \"fr\"] as const;\nexport const allergies = [\"peanuts\", \"dairy\", \"gluten\", \"soy\", \"shellfish\"] as const;\n\nexport const schema = z.object({\n\temail: z.string().email(\"Please enter a valid email.\"),\n\tbio: z.string().optional(),\n\ttheme: z.enum(themes).default(\"light\"),\n\tlanguage: z.enum(languages).default(\"en\"),\n\tmarketingEmails: z.boolean().default(true),\n\tallergies: z.array(z.enum(allergies)),\n});\n```\n\nLooking at the schema above, we know we'll need a few different input types to represent the different data types. Here's how we'll map the schema to input types:\n\n- `email` -> ` `\n- `bio` -> ` \n\nAnd that's it! You've now successfully built a settings form with Formsnap!\n\n## Next Steps\n\nNow that you've built your first form, you're ready to start building more complex forms with Formsnap & Superforms. Be sure to check out the rest of the documentation to learn more about the different components and APIs available to you.",
+ "toc": [
+ {
+ "title": "Installation",
+ "url": "#installation",
+ "items": []
+ },
+ {
+ "title": "Tutorial: Build a settings form",
+ "url": "#tutorial-build-a-settings-form",
+ "items": []
+ },
+ {
+ "title": "Next Steps",
+ "url": "#next-steps",
+ "items": []
+ }
+ ],
+ "section": "Anchors",
+ "slug": "quick-start",
+ "slugFull": "/quick-start"
+ },
+ {
+ "title": "Styling",
+ "description": "Easily style the various parts of your forms.",
+ "path": "styling",
+ "content": "Formsnap doesn't ship with any styles by default, but it does provide a number of ways to style the various parts of your form. You can use the class
prop to apply classes to the various components, or you can use the data
attributes to style the components using CSS.
\nData Attributes \nData attributes are applied to the various parts of your form so that you can easily style them using those attributes as selectors on a parent element or at the global level.
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nAttribute Description data-fs-error
Applied to all the formsnap elements within a field if the field has a validation error. Using this attribute, you can customize the appearance of the input, label, etc. when the field has a validation error. data-fs-control
Applied to the form control element used within a Control context. data-fs-label
Applied to the <label>
element rendered by the Label component. data-fs-field-errors
Applied to the FieldErrors container <div>
element. data-fs-field-error
Applied to the individually rendered <div>
elements for each of the errors in the FieldErrors component. data-fs-description
Applied to the <div>
element rendered by the Description component. data-fs-fieldset
Applied to the <fieldset>
element rendered by the Fieldset component. data-fs-legend
Applied to the <legend>
element rendered by the Legend component.
\nHere's an example of how you might use these data attributes to style the various parts of your form:
\n[data-fs-error] {\n\tcolor: red;\n}\n\n[data-fs-control] {\n\tborder: 1px solid #ccc;\n}\n\n/* ... */\n
\nCSS Frameworks \nIf you're using a CSS framework like TailwindCSS or UnoCSS, you can simply pass the class
prop to the various components that render HTML elements under the hood. For example:
\n<script lang=\"ts\">\n\timport { Label } from \"formsnap\";\n</script>\n\n<form>\n\t<!-- ... -->\n\t<Label class=\"text-black hover:text-orange-500\">First Name</Label>\n\t<!-- ... -->\n</form>\n
",
+ "raw": "Formsnap doesn't ship with any styles by default, but it does provide a number of ways to style the various parts of your form. You can use the `class` prop to apply classes to the various components, or you can use the `data` attributes to style the components using CSS.\n\n## Data Attributes\n\nData attributes are applied to the various parts of your form so that you can easily style them using those attributes as selectors on a parent element or at the global level.\n\n\n| Attribute | Description |\n| --------- | ----------- |\n| `data-fs-error` | Applied to all the formsnap elements within a field if the field has a validation error. Using this attribute, you can customize the appearance of the input, label, etc. when the field has a validation error. |\n| `data-fs-control` | Applied to the form control element used within a [Control](/docs/components/control) context. |\n| `data-fs-label` | Applied to the `` element rendered by the [Label](/docs/components/label) component. |\n| `data-fs-field-errors` | Applied to the [FieldErrors](/docs/components/validation-error) container `` element. |\n| `data-fs-field-error` | Applied to the individually rendered `
` elements for each of the errors in the [FieldErrors](/docs/components/validation-error) component. |\n| `data-fs-description` | Applied to the `
` element rendered by the [Description](/docs/components/description) component. |\n| `data-fs-fieldset` | Applied to the `
` element rendered by the [Fieldset](/docs/components/fieldset) component. |\n| `data-fs-legend` | Applied to the `` element rendered by the [Legend](/docs/components/legend) component. |\n\nHere's an example of how you might use these data attributes to style the various parts of your form:\n\n```css title=\"app.pcss\"\n[data-fs-error] {\n\tcolor: red;\n}\n\n[data-fs-control] {\n\tborder: 1px solid #ccc;\n}\n\n/* ... */\n```\n\n## CSS Frameworks\n\nIf you're using a CSS framework like TailwindCSS or UnoCSS, you can simply pass the `class` prop to the various components that render HTML elements under the hood. For example:\n\n```svelte {7}\n\n\n\n\t\n\tFirst Name \n\t\n \n```",
+ "toc": [
+ {
+ "title": "Data Attributes",
+ "url": "#data-attributes",
+ "items": []
+ },
+ {
+ "title": "CSS Frameworks",
+ "url": "#css-frameworks",
+ "items": []
+ }
+ ],
+ "section": "Anchors",
+ "slug": "styling",
+ "slugFull": "/styling"
+ },
+ {
+ "title": "child",
+ "description": "Use your own elements or components with Formsnap.",
+ "path": "composition/child",
+ "content": "\nAlthough the provided components are the recommended and easiest way to use Formsnap, they aren't the only way. If you prefer to bring your own components or use native HTML elements, that's fine too.
\nThe child
snippet is available on all components that render native HTML elements under the hood. When used, Formsnap won't render the default element and will expect you to spread the props
snippet onto a custom element or component of your choosing.
\nUsage Example \nIf you wanted to use your own custom Label
component or use scoped styles with a <label>
element, you can do so by using the child
snippet within the Label component.
\n<script lang=\"ts\">\n\timport { Label } from \"formsnap\";\n</script>\n\n<Label>\n\t{#snippet child({ props })}\n\t\t<label {...props} class=\"label\">Name</label>\n\t{/snippet}\n</Label>\n\n<style>\n\tlabel {\n\t\tcolor: green;\n\t}\n</style>\n
\n\nIf you plan to pass a custom ID to your custom element, you should first pass it to the component, which will then forward it down to the props
snippet prop.
\n ",
+ "raw": "\n\nAlthough the provided components are the recommended and easiest way to use Formsnap, they aren't the only way. If you prefer to bring your own components or use native HTML elements, that's fine too.\n\nThe `child` snippet is available on all components that render native HTML elements under the hood. When used, Formsnap won't render the default element and will expect you to spread the `props` snippet onto a custom element or component of your choosing.\n\n## Usage Example\n\nIf you wanted to use your own custom `Label` component or use scoped styles with a `` element, you can do so by using the `child` snippet within the [Label](/docs/components/label) component.\n\n```svelte {6-8}\n\n\n\n\t{#snippet child({ props })}\n\t\tName \n\t{/snippet}\n \n\n\n```\n\n\n\nIf you plan to pass a custom ID to your custom element, you should first pass it to the component, which will then forward it down to the `props` snippet prop.\n\n ",
+ "toc": [
+ {
+ "title": "Usage Example",
+ "url": "#usage-example",
+ "items": []
+ }
+ ],
+ "section": "Composition",
+ "slug": "composition/child",
+ "slugFull": "/composition/child"
+ },
+ {
+ "title": "useFormControl",
+ "description": "Use the form control's state for more advanced composition.",
+ "path": "composition/use-form-control",
+ "content": "\nYou can use useFormControl
within the context of a Control component to access the state of the control and use it as you see fit.
\nUsage \nSay we're building a custom component that contains both a custom input and label.
\n<script lang=\"ts\">\n\timport { useFormControl } from \"formsnap\";\n\timport CustomLabel from \"$lib/components/CustomLabel.svelte\";\n\timport CustomInput from \"$lib/components/CustomInput.svelte\";\n\n\tlet { label }: { label: string } = $props();\n\n\tconst control = useFormControl();\n</script>\n\n<CustomLabel {...control.labelProps}>\n\t{label}\n</CustomLabel>\n<CustomInput {...control.props} />\n
\nIn this example, we're using useFormControl
to get the spreadable for the label and control elements. We then pass those attributes to our custom components.
\nWe'd then use this within the context of a Control
component like so:
\n<Field {form} name=\"email\">\n\t<Control>\n\t\t<LabelInput label=\"Email address\" />\n\t</Control>\n</Field>\n
\nAPI Reference \nProps \n string | undefined | null\" name=\"id\">\nOptionally provide a getter function that returns a custom ID to override the default control ID.
\n \nReturn Type \nuseFormControl
returns the following types:
\nexport type FormControlContext = {\n\t/** Reactive state containing the ID of the form control. */\n\treadonly id: string;\n\n\t/** Reactive state containing the attributes for the label element. */\n\treadonly labelProps: Record<string, unknown>;\n\n\t/** Reactive state containing the attributes for the control element. */\n\treadonly props: Record<string, unknown>;\n};\n
\n\nThe useFormControl
function returns getters for the various reactive states. You should not destructure the object returned by useFormControl
and instead use the getters directly.
\n ",
+ "raw": "\n\nYou can use `useFormControl` within the context of a [Control](/docs/components/control) component to access the state of the control and use it as you see fit.\n\n## Usage\n\nSay we're building a custom component that contains both a custom input and label.\n\n```svelte title=\"LabelInput.svelte\"\n\n\n\n\t{label}\n \n \n```\n\nIn this example, we're using `useFormControl` to get the spreadable for the label and control elements. We then pass those attributes to our custom components.\n\nWe'd then use this within the context of a `Control` component like so:\n\n```svelte\n\n\t\n\t\t \n\t \n \n```\n\n## API Reference\n\n### Props\n\n string | undefined | null\" name=\"id\">\n\nOptionally provide a getter function that returns a custom ID to override the default control ID.\n\n \n\n### Return Type\n\n`useFormControl` returns the following types:\n\n```ts\nexport type FormControlContext = {\n\t/** Reactive state containing the ID of the form control. */\n\treadonly id: string;\n\n\t/** Reactive state containing the attributes for the label element. */\n\treadonly labelProps: Record;\n\n\t/** Reactive state containing the attributes for the control element. */\n\treadonly props: Record;\n};\n```\n\n\n\nThe `useFormControl` function returns getters for the various reactive states. You should not destructure the object returned by `useFormControl` and instead use the getters directly.\n\n ",
+ "toc": [
+ {
+ "title": "Usage",
+ "url": "#usage",
+ "items": []
+ },
+ {
+ "title": "API Reference",
+ "url": "#api-reference",
+ "items": [
+ {
+ "title": "Props",
+ "url": "#props",
+ "items": []
+ },
+ {
+ "title": "Return Type",
+ "url": "#return-type",
+ "items": []
+ }
+ ]
+ }
+ ],
+ "section": "Composition",
+ "slug": "composition/use-form-control",
+ "slugFull": "/composition/use-form-control"
+ },
+ {
+ "title": "useFormField",
+ "description": "Use the form field's state for advanced composition with custom form components.",
+ "path": "composition/use-form-field",
+ "content": "\nYou can use useFormField
within the context of a Field , Fieldset , or ElementField component to access the state of the field and use it to build more advanced form components.
\nUsage Example \nThe useFormField
function is provided for more advanced use cases where you may need to access the entire state of a form field, as well as the form itself.
\n<script lang=\"ts\">\n\timport { useFormField } from \"formsnap\";\n\timport type { HTMLAttributes } from \"svelte/elements\";\n\timport { schema } from \"./schema.js\";\n\timport type { Infer } from \"sveltekit-superforms\";\n\t// whatever your validation library is\n\timport { z } from \"zod\";\n\n\tlet { id, ...rest }: HTMLAttributes<HTMLElement> = $props();\n\n\tconst field = useFormField<Infer<typeof schema>, \"name\">({\n\t\terrorsId: () => id,\n\t});\n</script>\n\n{#if $errors.length > 0}\n\t<div class=\"error\" {id} {...rest}>\n\t\t{#each $errors as error}\n\t\t\t<p>{error}</p>\n\t\t{/each}\n\t</div>\n{/if}\n
\nIn the example above, we're using the useFormField
function to pass a custom errorsId
that will be used to apply appropriate ARIA attributes to the other components within the field.
\nAPI \nProps \n string | undefined | null\" name=\"errorsId\">\nOptionally provide a getter function that returns a custom ID to override the errors container ID.
\n \n string | undefined | null\" name=\"descriptionId\">\nOptionally provide a getter function that returns a custom ID to override the description container ID.
\n \nReturn Type \nuseFormField
returns the following type:
\nimport type { FormPath } from \"sveltekit-superforms\";\n\n/**\n * State for the current form field.\n */\nexport type UseFormFieldReturn<T extends Record<string, unknown>, U extends FormPath<T>> = {\n\t/** The original form store passed to the `*Field` component. */\n\tform: SuperForm<T>;\n\n\t/** Reactive state containing the ID of the field errors container for the field. */\n\treadonly errorsId: string;\n\n\t/** Reactive state containing the ID of the description element for the field. */\n\treadonly descriptionId: string;\n\n\t/** Reactive state containing the name of the field. */\n\treadonly name: U;\n\n\t/** Reactive state containing the current validations errors for the field. */\n\treadonly errors: string[];\n\n\t/** Reactive state containing the constraints (if any) for the field. */\n\treadonly constraints: Record<string, unknown>;\n\n\t/** Reactive state containing the tainted state of the field. */\n\treadonly tainted: boolean;\n};\n
\n\nThe useFormField
function returns getters for the various reactive states. You should not destructure the object returned by useFormField
and instead use the getters directly.
\n ",
+ "raw": "\n\nYou can use `useFormField` within the context of a [Field](/docs/components/field), [Fieldset](/docs/components/fieldset), or [ElementField](/docs/components/element-field) component to access the state of the field and use it to build more advanced form components.\n\n## Usage Example\n\nThe `useFormField` function is provided for more advanced use cases where you may need to access the entire state of a form field, as well as the form itself.\n\n```svelte title=\"CustomFieldErrors.svelte\"\n\n\n{#if $errors.length > 0}\n\t\n\t\t{#each $errors as error}\n\t\t\t
{error}
\n\t\t{/each}\n\t
\n{/if}\n```\n\nIn the example above, we're using the `useFormField` function to pass a custom `errorsId` that will be used to apply appropriate ARIA attributes to the other components within the field.\n\n## API\n\n### Props\n\n string | undefined | null\" name=\"errorsId\">\n\nOptionally provide a getter function that returns a custom ID to override the errors container ID.\n\n \n\n string | undefined | null\" name=\"descriptionId\">\n\nOptionally provide a getter function that returns a custom ID to override the description container ID.\n\n \n\n### Return Type\n\n`useFormField` returns the following type:\n\n```ts\nimport type { FormPath } from \"sveltekit-superforms\";\n\n/**\n * State for the current form field.\n */\nexport type UseFormFieldReturn, U extends FormPath> = {\n\t/** The original form store passed to the `*Field` component. */\n\tform: SuperForm;\n\n\t/** Reactive state containing the ID of the field errors container for the field. */\n\treadonly errorsId: string;\n\n\t/** Reactive state containing the ID of the description element for the field. */\n\treadonly descriptionId: string;\n\n\t/** Reactive state containing the name of the field. */\n\treadonly name: U;\n\n\t/** Reactive state containing the current validations errors for the field. */\n\treadonly errors: string[];\n\n\t/** Reactive state containing the constraints (if any) for the field. */\n\treadonly constraints: Record;\n\n\t/** Reactive state containing the tainted state of the field. */\n\treadonly tainted: boolean;\n};\n```\n\n\n\nThe `useFormField` function returns getters for the various reactive states. You should not destructure the object returned by `useFormField` and instead use the getters directly.\n\n ",
+ "toc": [
+ {
+ "title": "Usage Example",
+ "url": "#usage-example",
+ "items": []
+ },
+ {
+ "title": "API",
+ "url": "#api",
+ "items": [
+ {
+ "title": "Props",
+ "url": "#props",
+ "items": []
+ },
+ {
+ "title": "Return Type",
+ "url": "#return-type",
+ "items": []
+ }
+ ]
+ }
+ ],
+ "section": "Composition",
+ "slug": "composition/use-form-field",
+ "slugFull": "/composition/use-form-field"
+ },
+ {
+ "title": "Control",
+ "description": "Associates a label with and provides necessary attributes for a form control.",
+ "path": "components/control",
+ "content": "\nIn the context of a form, a control refers to any interactive element such as an input field, a select dropdown, or a button. This includes custom components like select dropdowns or checkboxes that function as buttons but still serve as form inputs, typically activated by clicking on a label or pressing a key.
\nEach control and its label should be wrapped in its own Control
component. This is important for accessibility, as it ensures that the label is associated with the control, and that the label is announced to screen readers when the control receives focus.
\n\nA common question is \"why we can't just include this logic in the various Field
components?\" .
\nDoing so would limit the Field
component to a single control, which would prevent them from being used for checkbox groups, radio groups, and other multi-control components. The APIs are flexible enough that you could create your own custom wrapper components to make them more convenient to use for your specific use case.
\n \nThe Control
component doesn't render an element itself, it strictly provides context and attributes for the control via a snippet prop and state for the Label .
\nUsage \n<Control>\n\t{#snippet children({ props })}\n\t\t<input type=\"text\" {...props} bind:value={$formData.name} />\n\t{/snippet}\n</Control>\n
\nAPI Reference \nProps \n\nOptionally provide a unique id for the form item/control. If not provided, a unique ID will be generated for you.
\nThis is useful when another library automatically generates IDs for form items. You can pass that ID to the id
prop and the label will be associated with that control.
\n \n\nThe children snippet is used to provide attributes for the control element/component.
\n \nComposition \nSince the Control
component doesn't render an HTML element, it's a common practice to create a wrapper component around it to have consistent styling and behavior across your forms.
\nFor example, you may want to automatically include the Label for each item, and you want the label and children content to be wrapped in a <div>
.
\nHere's how you might do just that:
\n<script lang=\"ts\">\n\timport { Control, Label } from \"formsnap\";\n\timport type { ComponentProps } from \"svelte\";\n\n\tlet {\n\t\tlabel,\n\t\t// Rename the children prop to childrenProp to avoid\n\t\t// conflicts with the Control component\n\t\tchildren: childrenProp,\n\t\t...restProps\n\t}: ComponentProps<typeof Control> & {\n\t\tlabel: string;\n\t} = $props();\n</script>\n\n<Control {...restProps}>\n\t{#snippet children({ props })}\n\t\t<div class=\"flex flex-col gap-2\">\n\t\t\t<Label>{label}</Label>\n\t\t\t<!-- Forward the props to the children snippet -->\n\t\t\t{@render childrenProp({ props })}\n\t\t</div>\n\t{/snippet}\n</Control>\n
",
+ "raw": "\n\nIn the context of a form, a **_control_** refers to any interactive element such as an input field, a select dropdown, or a button. This includes custom components like select dropdowns or checkboxes that function as buttons but still serve as form inputs, typically activated by clicking on a label or pressing a key.\n\nEach control and its label should be wrapped in its own `Control` component. This is important for accessibility, as it ensures that the label is associated with the control, and that the label is announced to screen readers when the control receives focus.\n\n\n\nA common question is _\"why we can't just include this logic in the various `Field` components?\"_.\n\nDoing so would limit the `Field` component to a single control, which would prevent them from being used for checkbox groups, radio groups, and other multi-control components. The APIs are flexible enough that you could create your own custom wrapper components to make them more convenient to use for your specific use case.\n\n \n\nThe `Control` component doesn't render an element itself, it strictly provides context and attributes for the control via a snippet prop and state for the [Label](/docs/components/label).\n\n## Usage\n\n```svelte title=\"+page.svelte\"\n\n\t{#snippet children({ props })}\n\t\t \n\t{/snippet}\n \n```\n\n## API Reference\n\n### Props\n\n\n\nOptionally provide a unique id for the form item/control. If not provided, a unique ID will be generated for you.\n\nThis is useful when another library automatically generates IDs for form items. You can pass that ID to the `id` prop and the label will be associated with that control.\n\n \n\n\n\nThe children snippet is used to provide attributes for the control element/component.\n\n \n\n## Composition\n\nSince the `Control` component doesn't render an HTML element, it's a common practice to create a wrapper component around it to have consistent styling and behavior across your forms.\n\nFor example, you may want to automatically include the [Label](/docs/components/label) for each item, and you want the label and children content to be wrapped in a ``.\n\nHere's how you might do just that:\n\n```svelte title=\"CustomControl.svelte\"\n\n\n
\n\t{#snippet children({ props })}\n\t\t\n\t\t\t{label} \n\t\t\t\n\t\t\t{@render childrenProp({ props })}\n\t\t
\n\t{/snippet}\n \n```",
+ "toc": [
+ {
+ "title": "Usage",
+ "url": "#usage",
+ "items": []
+ },
+ {
+ "title": "API Reference",
+ "url": "#api-reference",
+ "items": [
+ {
+ "title": "Props",
+ "url": "#props",
+ "items": []
+ }
+ ]
+ },
+ {
+ "title": "Composition",
+ "url": "#composition",
+ "items": []
+ }
+ ],
+ "section": "Components",
+ "slug": "components/control",
+ "slugFull": "/components/control"
+ },
+ {
+ "title": "Description",
+ "description": "Provides an accessible description for a form field.",
+ "path": "components/description",
+ "content": "\n
The Description
component provides an accessible description for a field. It renders a <div>
element and should be used to provide additional context or instructions for a form field.
\n
Descriptions must be used within the context of a Field , Fieldset , or ElementField component and will automatically be linked to the Control of the field using the aria-describedby
attribute.
\n
Usage \n
<Field name=\"name\" {form}>\n\t<Control>\n\t\t{#snippet children({ props })}\n\t\t\t<Label>Name</Label>\n\t\t\t<input type=\"text\" {...attrs} />\n\t\t{/snippet}\n\t</Control>\n\t<Description>Your full name, including your middle name.</Description>\n</Field>\n
\n
API Reference \n
Props \n
The Description
component accepts all props that a standard HTML <div>
element would accept, along with a few additional props:
\n
\nA reference to the underlying HTML element rendered by the Description
component.
\n<Description bind:ref={descriptionRef}>\n\t<!-- ... -->\n</Description>\n
\n \n
\nIf provided, the Description
component will not render an HTML element and will instead expect you to spread the snippet's props
onto an element of your choosing.
\nSee the child
snippet documentation for more information.
\n \n
\" name=\"...rest\">\nAny additional props provided to the Description
component will be spread onto the underlying HTML element.
\n \n
Data Attributes \n
The following data attributes are automatically applied to the <div>
element rendered by the Description
component.
\n
\nApplied to the description element for selection during styling or otherwise.
\n \n
\nApplied to the description element when a validation error exists on the field.
\n ",
+ "raw": "\n\nThe `Description` component provides an accessible description for a field. It renders a `
` element and should be used to provide additional context or instructions for a form field.\n\nDescriptions must be used within the context of a [Field](/docs/components/field), [Fieldset](/docs/components/fieldset), or [ElementField](/docs/components/element-field) component and will automatically be linked to the [Control](/docs/components/control) of the field using the `aria-describedby` attribute.\n\n## Usage\n\n```svelte {8}\n
\n\t\n\t\t{#snippet children({ props })}\n\t\t\tName \n\t\t\t \n\t\t{/snippet}\n\t \n\tYour full name, including your middle name. \n \n```\n\n## API Reference\n\n### Props\n\nThe `Description` component accepts all props that a standard HTML `
` element would accept, along with a few additional props:\n\n
\n\nA reference to the underlying HTML element rendered by the `Description` component.\n\n```svelte /bind:ref={descriptionRef}/\n\n\t\n \n```\n\n \n\n
\n\nIf provided, the `Description` component will not render an HTML element and will instead expect you to spread the snippet's `props` onto an element of your choosing.\n\nSee the [`child`](/docs/composition/child) snippet documentation for more information.\n\n \n\n
\" name=\"...rest\">\n\nAny additional props provided to the `Description` component will be spread onto the underlying HTML element.\n\n \n\n### Data Attributes\n\nThe following data attributes are automatically applied to the `
` element rendered by the `Description` component.\n\n
\n\nApplied to the description element for selection during styling or otherwise.\n\n \n\n
\n\nApplied to the description element when a validation error exists on the field.\n\n ",
+ "toc": [
+ {
+ "title": "Usage",
+ "url": "#usage",
+ "items": []
+ },
+ {
+ "title": "API Reference",
+ "url": "#api-reference",
+ "items": [
+ {
+ "title": "Props",
+ "url": "#props",
+ "items": []
+ },
+ {
+ "title": "Data Attributes",
+ "url": "#data-attributes",
+ "items": []
+ }
+ ]
+ }
+ ],
+ "section": "Components",
+ "slug": "components/description",
+ "slugFull": "/components/description"
+ },
+ {
+ "title": "ElementField",
+ "description": "Provides the necessary context for a form field that represents a single element in an array.",
+ "path": "components/element-field",
+ "content": "\n
The ElementField
component is used to treat each element of an array as a separate form field. It's useful when you have a dynamic list of items that you want to treat as separate fields in your form.
\n
ElementField
s should be used within the context of a Field or Fieldset component. ElementField
s create their own context to scope the errors and other states of the field.
\n
Usage \n
Here's an example of how you might use the ElementField
component to create a dynamic list of URLs in a form.
\n
<script lang=\"ts\">\n\timport { ElementField, FieldErrors, Control, Label, Fieldset, Description } from \"formsnap\";\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { z } from \"zod\";\n\n\tconst schema = z.object({\n\t\turls: z.array(z.string().url()).min(2).default([\"\", \"\"]),\n\t});\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\n\tconst { form: formData, enhance } = form;\n</script>\n\n<form use:enhance method=\"POST\">\n\t<Fieldset {form} name=\"urls\">\n\t\t<Legend>Enter your URLS</Legend>\n\t\t{#each $formData.urls as _, i}\n\t\t\t<ElementField {form} name=\"urls[{i}]\">\n\t\t\t\t<Control>\n\t\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t\t<input type=\"url\" bind:value={$formData.urls[i]} {...props} />\n\t\t\t\t\t{/snippet}\n\t\t\t\t</Control>\n\t\t\t\t<FieldErrors />\n\t\t\t</ElementField>\n\t\t{/each}\n\t\t<Description>Your URLs will be displayed on your public profile.</Description>\n\t\t<FieldErrors />\n\t</Fieldset>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\n
We're able to display errors for each element of the array, as well as array-level errors for the entire fieldset.
\n
Check out the Dynamic Fields recipe for more advanced usage of the ElementField
component.
\n
\nThe ElementField
component doesn't render an element, it strictly provides context for its children.
\n \n
API Reference \n
Props \n
\" name=\"form\" required>\nThe form object returned from calling superForm
in your component.
\n \n
\" name=\"name\" required>\nThe path to the field in the form object.
\n \n
Snippet Props \n
The following snippet props are provided to the children
snippet for convenience and ease of composition when using the ElementField
component.
\n
\nThe value of the value store of the field.
\n \n
\nThe value of the errors store for the field.
\n \n
\" name=\"constraints\">\nThe constraints for the field.
\n \n
\nWhether the field is tainted or not.
\n \n
Composition \n
Since the ElementField
component doesn't render any HTML elements, it's common practice to create a wrapper component around it to have consistent styling and behavior across your forms.
\n
For example, you may always want to render the FieldErrors component for every field. Rather than manually including it each time, you can create a wrapper <CustomElementField />
component that includes it automatically.
\n
To maintain the type safety of the component, we'll need to use some generics, which eslint sometimes complains about, so if you see a warning, it's likely a false positive and you can ignore it.
\n
<script lang=\"ts\" module>\n\timport type { FormPathArrays, FormPathLeaves } from \"sveltekit-superforms\";\n\ttype T = Record<string, unknown>;\n\ttype U = unknown;\n</script>\n\n<script lang=\"ts\" generics=\"T extends Record<string, unknown>, U extends FormPathLeaves<T>\">\n\timport { ElementField, type ElementFieldProps, FieldErrors } from \"formsnap\";\n\timport type { SuperForm } from \"sveltekit-superforms\";\n\n\tlet { form, name, children: childrenProp }: ElementFieldProps<T, U> = $props();\n</script>\n\n<ElementField {form} {name}>\n\t{#snippet children(snippetProps)}\n\t\t{@render childrenProp(snippetProps)}\n\t{/snippet}\n\t<FieldErrors />\n</ElementField>\n
",
+ "raw": "\n\nThe `ElementField` component is used to treat each element of an array as a separate form field. It's useful when you have a dynamic list of items that you want to treat as separate fields in your form.\n\n`ElementField`s should be used within the context of a [Field](/docs/components/field) or [Fieldset](/docs/components/fieldset) component. `ElementField`s create their own context to scope the errors and other states of the field.\n\n## Usage\n\nHere's an example of how you might use the `ElementField` component to create a dynamic list of URLs in a form.\n\n```svelte title=\"+page.svelte\"\n\n\n
\n\t\n\t\tEnter your URLS \n\t\t{#each $formData.urls as _, i}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t\t \n\t\t\t\t\t{/snippet}\n\t\t\t\t \n\t\t\t\t \n\t\t\t \n\t\t{/each}\n\t\tYour URLs will be displayed on your public profile. \n\t\t \n\t \n\tSubmit \n \n```\n\nWe're able to display errors for each element of the array, as well as array-level errors for the entire fieldset.\n\nCheck out the [Dynamic Fields](/docs/recipes/dynamic-fields) recipe for more advanced usage of the `ElementField` component.\n\n
\n\nThe `ElementField` component doesn't render an element, it strictly provides context for its children.\n\n \n\n## API Reference\n\n### Props\n\n
\" name=\"form\" required>\n\nThe form object returned from calling `superForm` in your component.\n\n \n\n
\" name=\"name\" required>\n\nThe path to the field in the form object.\n\n \n\n### Snippet Props\n\nThe following snippet props are provided to the `children` snippet for convenience and ease of composition when using the `ElementField` component.\n\n
\n\nThe value of the value store of the field.\n\n \n\n
\n\nThe value of the errors store for the field.\n\n \n\n
\" name=\"constraints\">\n\nThe constraints for the field.\n\n \n\n
\n\nWhether the field is tainted or not.\n\n \n\n## Composition\n\nSince the `ElementField` component doesn't render any HTML elements, it's common practice to create a wrapper component around it to have consistent styling and behavior across your forms.\n\nFor example, you may always want to render the [FieldErrors](/docs/components/field-errors) component for every field. Rather than manually including it each time, you can create a wrapper `
` component that includes it automatically.\n\nTo maintain the type safety of the component, we'll need to use some generics, which eslint sometimes complains about, so if you see a warning, it's likely a false positive and you can ignore it.\n\n```svelte title=\"CustomElementField.svelte\"\n\n\n\n\n
\n\t{#snippet children(snippetProps)}\n\t\t{@render childrenProp(snippetProps)}\n\t{/snippet}\n\t \n \n```",
+ "toc": [
+ {
+ "title": "Usage",
+ "url": "#usage",
+ "items": []
+ },
+ {
+ "title": "API Reference",
+ "url": "#api-reference",
+ "items": [
+ {
+ "title": "Props",
+ "url": "#props",
+ "items": []
+ },
+ {
+ "title": "Snippet Props",
+ "url": "#snippet-props",
+ "items": []
+ }
+ ]
+ },
+ {
+ "title": "Composition",
+ "url": "#composition",
+ "items": []
+ }
+ ],
+ "section": "Components",
+ "slug": "components/element-field",
+ "slugFull": "/components/element-field"
+ },
+ {
+ "title": "FieldErrors",
+ "description": "The container for validation errors for a Field, Fieldset, or ElementField.",
+ "path": "components/field-errors",
+ "content": "\n
The FieldErrors
component renders the following structure by default (attributes omitted for brevity):
\n
<div>\n\t{#if children}\n\t\t{@render children({ errors, errorProps })}\n\t{:else}\n\t\t{#each errors as error}\n\t\t\t<div>{error}</div>\n\t\t{/each}\n\t{/if}\n</div>\n
\n
Notice that we're populating the fallback for the children snippet, so if you don't provide children content for the FieldErrors
component, it will render a <div>
element for each error in the errors
array.
\n
The errors
are the errors for the Field , Fieldset , or ElementField that the FieldErrors
component is associated with and must be used within the context of one of those components.
\n
The errors container is automatically linked to the control of the field using the aria-describedby
attribute when errors are present.
\n
Usage \n
Basic Usage \n
By default, the FieldErrors
component will render a <div>
element with the errors for the field it is associated with.
\n
<Field {form} name=\"name\">\n\t<Control>\n\t\t{#snippet children({ props })}\n\t\t\t<Label>Name</Label>\n\t\t\t<input type=\"text\" {...props} />\n\t\t{/snippet}\n\t</Control>\n\t<FieldErrors />\n</Field>\n
\n
Custom Error Rendering \n
If you want to customize the rendering of the errors, you can access the errors using the errors
snippet prop and render them however you'd like.
\n
<Field {form} name=\"name\">\n\t<Control>\n\t\t{#snippet children({ props })}\n\t\t\t<Label>Name</Label>\n\t\t\t<input type=\"text\" {...props} />\n\t\t{/snippet}\n\t</Control>\n\t<FieldErrors>\n\t\t{#snippet children({ errors, errorProps })}\n\t\t\t{#each errors as err}\n\t\t\t\t<span style=\"color: red;\" {...errorProps}>{err}</span>\n\t\t\t{/each}\n\t\t{/snippet}\n\t</FieldErrors>\n</Field>\n
\n
API Reference \n
Props \n
The FieldErrors
component accepts all props that a standard HTML <div>
element would accept along with a few additional props:
\n
\nA reference to the underlying HTML element rendered by the Description
component.
\n<Description bind:ref={descriptionRef}>\n\t<!-- ... -->\n</Description>\n
\n \n
\nIf provided, the FieldErrors
component will not render an HTML element and will instead expect you to spread the snippet's props
onto an element of your choosing.
\nSee the child
snippet documentation for more information.
\n \n
\" name=\"...rest\">\nAny additional props provided to the FieldErrors
component will be spread onto the underlying HTML element.
\n \n
Attributes \n
Field Errors Container \n
The following attributes are automatically applied to the container rendered by the FieldErrors
component. This is also the shape of the props
snippet prop when using the child snippet.
\n
export type FieldErrorsAttrs = {\n\t/** The ID of the field error container element, used to describe the control. */\n\tid: string;\n\n\t/** Present when a validation error exists on the field. */\n\t\"data-fs-error\": string | undefined;\n\n\t/** Used for selection during styling or otherwise */\n\t\"data-fs-field-errors\": string;\n\n\t/** Notifies screen readers when a validation error occurs */\n\t\"aria-live\": \"assertive\" | \"polite\";\n\n\t/** Any additional props provided to `<Form.Validation />` */\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t[key: string]: any;\n};\n
\n
Error Elements \n
The following attributes are automatically applied to the individual error elements rendered by the FieldErrors
component. This is also the shape of the errorProps
snippet prop.
\n
export type ErrorAttrs = {\n\t/** Used for selection during styling or otherwise */\n\t\"data-fs-field-error\": string;\n\n\t/** Present when a validation error exists on the field. */\n\t\"data-fs-error\": string | undefined;\n};\n
",
+ "raw": "\n\nThe `FieldErrors` component renders the following structure by default (attributes omitted for brevity):\n\n```svelte\n
\n\t{#if children}\n\t\t{@render children({ errors, errorProps })}\n\t{:else}\n\t\t{#each errors as error}\n\t\t\t
{error}
\n\t\t{/each}\n\t{/if}\n
\n```\n\nNotice that we're populating the fallback for the children snippet, so if you don't provide children content for the `FieldErrors` component, it will render a `
` element for each error in the `errors` array.\n\nThe `errors` are the errors for the [Field](/docs/components/field), [Fieldset](/docs/components/fieldset), or [ElementField](/docs/components/element-field) that the `FieldErrors` component is associated with and must be used within the context of one of those components.\n\nThe errors container is automatically linked to the control of the field using the `aria-describedby` attribute when errors are present.\n\n## Usage\n\n### Basic Usage\n\nBy default, the `FieldErrors` component will render a `
` element with the errors for the field it is associated with.\n\n```svelte {8}\n
\n\t\n\t\t{#snippet children({ props })}\n\t\t\tName \n\t\t\t \n\t\t{/snippet}\n\t \n\t \n \n```\n\n### Custom Error Rendering\n\nIf you want to customize the rendering of the errors, you can access the errors using the `errors` snippet prop and render them however you'd like.\n\n```svelte {8-14}\n
\n\t\n\t\t{#snippet children({ props })}\n\t\t\tName \n\t\t\t \n\t\t{/snippet}\n\t \n\t\n\t\t{#snippet children({ errors, errorProps })}\n\t\t\t{#each errors as err}\n\t\t\t\t{err} \n\t\t\t{/each}\n\t\t{/snippet}\n\t \n \n```\n\n## API Reference\n\n### Props\n\nThe `FieldErrors` component accepts all props that a standard HTML `
` element would accept along with a few additional props:\n\n
\n\nA reference to the underlying HTML element rendered by the `Description` component.\n\n```svelte /bind:ref={descriptionRef}/\n\n\t\n \n```\n\n \n\n
\n\nIf provided, the `FieldErrors` component will not render an HTML element and will instead expect you to spread the snippet's `props` onto an element of your choosing.\n\nSee the [`child`](/docs/composition/child) snippet documentation for more information.\n\n \n\n
\" name=\"...rest\">\n\nAny additional props provided to the `FieldErrors` component will be spread onto the underlying HTML element.\n\n \n\n### Attributes\n\n#### Field Errors Container\n\nThe following attributes are automatically applied to the container rendered by the `FieldErrors` component. This is also the shape of the `props` snippet prop when using the [child](/docs/composition/child) snippet.\n\n```ts\nexport type FieldErrorsAttrs = {\n\t/** The ID of the field error container element, used to describe the control. */\n\tid: string;\n\n\t/** Present when a validation error exists on the field. */\n\t\"data-fs-error\": string | undefined;\n\n\t/** Used for selection during styling or otherwise */\n\t\"data-fs-field-errors\": string;\n\n\t/** Notifies screen readers when a validation error occurs */\n\t\"aria-live\": \"assertive\" | \"polite\";\n\n\t/** Any additional props provided to `
` */\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\t[key: string]: any;\n};\n```\n\n#### Error Elements\n\nThe following attributes are automatically applied to the individual error elements rendered by the `FieldErrors` component. This is also the shape of the `errorProps` snippet prop.\n\n```ts\nexport type ErrorAttrs = {\n\t/** Used for selection during styling or otherwise */\n\t\"data-fs-field-error\": string;\n\n\t/** Present when a validation error exists on the field. */\n\t\"data-fs-error\": string | undefined;\n};\n```",
+ "toc": [
+ {
+ "title": "Usage",
+ "url": "#usage",
+ "items": [
+ {
+ "title": "Basic Usage",
+ "url": "#basic-usage",
+ "items": []
+ },
+ {
+ "title": "Custom Error Rendering",
+ "url": "#custom-error-rendering",
+ "items": []
+ }
+ ]
+ },
+ {
+ "title": "API Reference",
+ "url": "#api-reference",
+ "items": [
+ {
+ "title": "Props",
+ "url": "#props",
+ "items": []
+ },
+ {
+ "title": "Attributes",
+ "url": "#attributes",
+ "items": [
+ {
+ "title": "Field Errors Container",
+ "url": "#field-errors-container",
+ "items": []
+ },
+ {
+ "title": "Error Elements",
+ "url": "#error-elements",
+ "items": []
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "section": "Components",
+ "slug": "components/field-errors",
+ "slugFull": "/components/field-errors"
+ },
+ {
+ "title": "Field",
+ "description": "Provides the necessary context for a form field.",
+ "path": "components/field",
+ "content": "\n
The Field
component provides the necessary context for its children to react\nto changes in the form state, as well as provides necessary information about the field,\nsuch as the ids needed for aria attributes, and a lot more.
\n
Each Field
creates its own context, and the children of the field only access\nthe immediate parent's context.
\n
\nThe Field
component doesn't render an element, it strictly provides context.
\n \n
API Reference \n
Props \n
\" name=\"form\" required>\nThe form object returned from calling superForm
in your component.
\n \n
\" name=\"name\" required>\nThe path to the field in the form object.
\n \n
Snippet Props \n
The following snippet props are provided to the children
snippet for convenience and ease of composition when using the Field
component.
\n
\nThe value of the value store of the field.
\n \n
\nThe value of the errors store for the field.
\n \n
\" name=\"constraints\">\nThe constraints for the field.
\n \n
\nWhether the field is tainted or not.
\n \n
Composition \n
Since the Field
component doesn't render any HTML elements, it's a common practice to create a wrapper component around it to have consistent styling and behavior across your forms.
\n
For example, you may always want to render the FieldErrors component for every field. Instead of manually including it every time, you can create a wrapper <CustomField />
component that includes it automatically.
\n
To maintain the type safety of the component, we'll need to use some generics, which eslint sometimes complains about, so if you see a warning, it's likely a false positive and you can ignore it.
\n
<script lang=\"ts\" module>\n\timport type { FormPath } from \"sveltekit-superforms\";\n\n\t// the form object\n\ttype T = Record<string, unknown>;\n\t// the path/name of the field in the form object\n\ttype U = unknown;\n</script>\n\n<script lang=\"ts\" generics=\"T extends Record<string, unknown>, U extends FormPath<T>\">\n\timport { Field, type FieldProps, FieldErrors } from \"formsnap\";\n\timport type { SuperForm } from \"sveltekit-superforms\";\n\n\tlet { form, name, children: childrenProp }: FieldProps<T, U> = $props();\n</script>\n\n<Field {form} {name}>\n\t{#snippet children(snippetProps)}\n\t\t{@render childrenProp(snippetProps)}\n\t\t<FieldErrors />\n\t{/snippet}\n</Field>\n
",
+ "raw": "\n\nThe `Field` component provides the necessary context for its children to react\nto changes in the form state, as well as provides necessary information about the field,\nsuch as the ids needed for aria attributes, and a lot more.\n\nEach `Field` creates its own context, and the children of the field only access\nthe immediate parent's context.\n\n
\n\nThe `Field` component doesn't render an element, it strictly provides context.\n\n \n\n## API Reference\n\n### Props\n\n
\" name=\"form\" required>\n\nThe form object returned from calling `superForm` in your component.\n\n \n\n
\" name=\"name\" required>\n\nThe path to the field in the form object.\n\n \n\n### Snippet Props\n\nThe following snippet props are provided to the `children` snippet for convenience and ease of composition when using the `Field` component.\n\n
\n\nThe value of the value store of the field.\n\n \n\n
\n\nThe value of the errors store for the field.\n\n \n\n
\" name=\"constraints\">\n\nThe constraints for the field.\n\n \n\n
\n\nWhether the field is tainted or not.\n\n \n\n## Composition\n\nSince the `Field` component doesn't render any HTML elements, it's a common practice to create a wrapper component around it to have consistent styling and behavior across your forms.\n\nFor example, you may always want to render the [FieldErrors](/docs/components/field-errors) component for every field. Instead of manually including it every time, you can create a wrapper `
` component that includes it automatically.\n\nTo maintain the type safety of the component, we'll need to use some generics, which eslint sometimes complains about, so if you see a warning, it's likely a false positive and you can ignore it.\n\n```svelte title=\"CustomField.svelte\"\n\n\n\n\n
\n\t{#snippet children(snippetProps)}\n\t\t{@render childrenProp(snippetProps)}\n\t\t \n\t{/snippet}\n \n```",
+ "toc": [
+ {
+ "title": "API Reference",
+ "url": "#api-reference",
+ "items": [
+ {
+ "title": "Props",
+ "url": "#props",
+ "items": []
+ },
+ {
+ "title": "Snippet Props",
+ "url": "#snippet-props",
+ "items": []
+ }
+ ]
+ },
+ {
+ "title": "Composition",
+ "url": "#composition",
+ "items": []
+ }
+ ],
+ "section": "Components",
+ "slug": "components/field",
+ "slugFull": "/components/field"
+ },
+ {
+ "title": "Fieldset",
+ "description": "Groups related form controls or fields and extends the Field component.",
+ "path": "components/fieldset",
+ "content": "\n
The Fieldset
component is used to follow the W3C Grouping Controls recommendation for associating related form controls. It renders a <fieldset>
element and should always be used in conjunction with the Legend component to provide a title for the group.
\n
This component automatically includes the Field component, so you don't need to worry about wrapping it yourself, just be sure to pass the form
and name
props to the Fieldset
as you would with the Field
component.
\n
When to use a fieldset \n
Radio Groups \n
When you have a group of radio buttons related to a single field, you should use a Fieldset
to group them together.
\n
<Fieldset {form} name=\"theme\">\n\t<Legend>Select your theme</Legend>\n\t{#each themes as theme}\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<input {...props} type=\"radio\" bind:group={$formData.theme} value={theme} />\n\t\t\t\t<Label>{theme}</Label>\n\t\t\t{/snippet}\n\t\t</Control>\n\t{/each}\n\t<Description>Help us understand your preferences by selecting a theme.</Description>\n\t<FieldErrors />\n</Fieldset>\n
\n
Checkbox Groups \n
When you have a group of checkboxes related to a single field, typically used for multiple selections, you should use a Fieldset
to group them together.
\n
<Fieldset {form} name=\"allergies\">\n\t<Legend>Any food allergies?</Legend>\n\t{#each allergies as allergy}\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<input\n\t\t\t\t\t{...props}\n\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\tbind:group={$formData.allergies}\n\t\t\t\t\tvalue={allergy}\n\t\t\t\t/>\n\t\t\t\t<Label>{allergy}</Label>\n\t\t\t{/snippet}\n\t\t</Control>\n\t{/each}\n\t<Description>We'll make sure to accommodate your dietary needs.</Description>\n\t<FieldErrors />\n</Fieldset>\n
\n
Grouped Form Sections \n
When you have a large form with multiple sections containing related fields, such as a \"Billing Address\" and a \"Shipping Address\", you should use a <fieldset>
to group the related fields together. You won't use the Fieldset
component directly in this case, since it doesn't represent a field on the form.
\n
<form>\n\t<fieldset>\n\t\t<legend>Billing Address</legend>\n\t\t<!-- ... billing address fields -->\n\t</fieldset>\n\t<fieldset>\n\t\t<legend>Shipping Address</legend>\n\t\t<!-- ... shipping address fields -->\n\t</fieldset>\n</form>\n
\n
API Reference \n
Props \n
The Fieldset
component renders a <fieldset>
element and accepts the following props:
\n
\" name=\"form\" required>\nThe form object returned from calling superForm
in your component.
\n \n
\" name=\"name\" required>\nThe path to the field in the form object.
\n \n
\nA $bindable
reference to the underlying HTML element rendered by the Fieldset
component.
\n \n
\nIf provided, the Fieldset
component will not render an HTML element and will instead expect you to spread the snippet's props
onto an element of your choosing.
\nSee the child
snippet documentation for more information.
\n \n
\" name=\"...rest\">\nAny additional props provided to the Fieldset
component will be spread onto the underlying HTML element.
\n \n
Snippet Props (children) \n
The following snippet props are provided to the children
snippet for convenience and ease of composition when using the ElementField
component.
\n
\nThe value of the value store of the field.
\n \n
\nThe value of the errors store for the field.
\n \n
\" name=\"constraints\">\nThe constraints for the field.
\n \n
\nWhether the field is tainted or not.
\n \n
Snippet Props (child) \n
The following snippet props are provided to the child
snippet for convenience and ease of composition when using the ElementField
component.
\n
\" name=\"props\">\nThe props to spread onto your custom <fieldset>
element/component.
\n \n
\nThe value of the value store of the field.
\n \n
\nThe value of the errors store for the field.
\n \n
\" name=\"constraints\">\nThe constraints for the field.
\n \n
\nWhether the field is tainted or not.
\n \n
Data Attributes \n
The following data attributes are automatically applied to the <fieldset>
element rendered by the Fieldset
component.
\n
\nApplied to the element for selection during styling or otherwise.
\n \n
\nApplied to the element when a validation error exists on the field.
\n ",
+ "raw": "\n\nThe `Fieldset` component is used to follow the [W3C Grouping Controls](https://www.w3.org/WAI/tutorials/forms/grouping/#associating-related-controls-with-fieldset) recommendation for associating related form controls. It renders a `
` element and should always be used in conjunction with the [Legend](/docs/components/legend) component to provide a title for the group.\n\nThis component automatically includes the [Field](/docs/components/field) component, so you don't need to worry about wrapping it yourself, just be sure to pass the `form` and `name` props to the `Fieldset` as you would with the `Field` component.\n\n## When to use a fieldset\n\n### Radio Groups\n\nWhen you have a group of radio buttons related to a single field, you should use a `Fieldset` to group them together.\n\n```svelte {1-2,13}\n\n\tSelect your theme \n\t{#each themes as theme}\n\t\t\n\t\t\t{#snippet children({ props })}\n\t\t\t\t \n\t\t\t\t{theme} \n\t\t\t{/snippet}\n\t\t \n\t{/each}\n\tHelp us understand your preferences by selecting a theme. \n\t \n \n```\n\n### Checkbox Groups\n\nWhen you have a group of checkboxes related to a single field, typically used for multiple selections, you should use a `Fieldset` to group them together.\n\n```svelte {1-2,18}\n\n\tAny food allergies? \n\t{#each allergies as allergy}\n\t\t\n\t\t\t{#snippet children({ props })}\n\t\t\t\t \n\t\t\t\t{allergy} \n\t\t\t{/snippet}\n\t\t \n\t{/each}\n\tWe'll make sure to accommodate your dietary needs. \n\t \n \n```\n\n### Grouped Form Sections\n\nWhen you have a large form with multiple sections containing related fields, such as a \"Billing Address\" and a \"Shipping Address\", you should use a `` to group the related fields together. You won't use the `Fieldset` component directly in this case, since it doesn't represent a field on the form.\n\n```svelte\n\n\t\n\t\tBilling Address \n\t\t\n\t \n\t\n\t\tShipping Address \n\t\t\n\t \n \n```\n\n## API Reference\n\n### Props\n\nThe `Fieldset` component renders a `` element and accepts the following props:\n\n\" name=\"form\" required>\n\nThe form object returned from calling `superForm` in your component.\n\n \n\n\" name=\"name\" required>\n\nThe path to the field in the form object.\n\n \n\n\n\nA `$bindable` reference to the underlying HTML element rendered by the `Fieldset` component.\n\n \n\n\n\nIf provided, the `Fieldset` component will not render an HTML element and will instead expect you to spread the snippet's `props` onto an element of your choosing.\n\nSee the [`child`](/docs/composition/child) snippet documentation for more information.\n\n \n\n\" name=\"...rest\">\n\nAny additional props provided to the `Fieldset` component will be spread onto the underlying HTML element.\n\n \n\n### Snippet Props (children)\n\nThe following snippet props are provided to the `children` snippet for convenience and ease of composition when using the `ElementField` component.\n\n\n\nThe value of the value store of the field.\n\n \n\n\n\nThe value of the errors store for the field.\n\n \n\n\" name=\"constraints\">\n\nThe constraints for the field.\n\n \n\n\n\nWhether the field is tainted or not.\n\n \n\n### Snippet Props (child)\n\nThe following snippet props are provided to the `child` snippet for convenience and ease of composition when using the `ElementField` component.\n\n\" name=\"props\">\n\nThe props to spread onto your custom `` element/component.\n\n \n\n\n\nThe value of the value store of the field.\n\n \n\n\n\nThe value of the errors store for the field.\n\n \n\n\" name=\"constraints\">\n\nThe constraints for the field.\n\n \n\n\n\nWhether the field is tainted or not.\n\n \n\n### Data Attributes\n\nThe following data attributes are automatically applied to the `` element rendered by the `Fieldset` component.\n\n\n\nApplied to the element for selection during styling or otherwise.\n\n \n\n\n\nApplied to the element when a validation error exists on the field.\n\n ",
+ "toc": [
+ {
+ "title": "When to use a fieldset",
+ "url": "#when-to-use-a-fieldset",
+ "items": [
+ {
+ "title": "Radio Groups",
+ "url": "#radio-groups",
+ "items": []
+ },
+ {
+ "title": "Checkbox Groups",
+ "url": "#checkbox-groups",
+ "items": []
+ },
+ {
+ "title": "Grouped Form Sections",
+ "url": "#grouped-form-sections",
+ "items": []
+ }
+ ]
+ },
+ {
+ "title": "API Reference",
+ "url": "#api-reference",
+ "items": [
+ {
+ "title": "Props",
+ "url": "#props",
+ "items": []
+ },
+ {
+ "title": "Snippet Props (children)",
+ "url": "#snippet-props-children",
+ "items": []
+ },
+ {
+ "title": "Snippet Props (child)",
+ "url": "#snippet-props-child",
+ "items": []
+ },
+ {
+ "title": "Data Attributes",
+ "url": "#data-attributes",
+ "items": []
+ }
+ ]
+ }
+ ],
+ "section": "Components",
+ "slug": "components/fieldset",
+ "slugFull": "/components/fieldset"
+ },
+ {
+ "title": "Label",
+ "description": "Renders a label element for a control.",
+ "path": "components/label",
+ "content": "\nThe Label
component must be used as a child of a Control component. It renders a <label>
element and includes the necessary attributes to associate it with the control.
\nUsage \nWhen using a Label
inside a Control , you don't need to worry about the for
attribute. Formsnap handles that for you.
\n<Field {form} name=\"name\">\n\t<Control>\n\t\t{#snippet children({ props })}\n\t\t\t<Label>Name</Label>\n\t\t\t<input type=\"text\" {...props} />\n\t\t{/snippet}\n\t</Control>\n</Field>\n
\nAPI Reference \nProps \n\nA $bindable
reference to the underlying <label>
element rendered by the Label
component.
\n \n\nIf provided, the Label
component will not render an HTML element and will instead expect you to spread the snippet's props
onto an element of your choosing.
\nSee the child
snippet documentation for more information.
\n \n\" name=\"...rest\">\nAny additional props provided to the Label
component will be spread onto the underlying HTML element.
\n \nData Attributes \nThe following data attributes are automatically applied to the element rendered by the Label
component.
\n\nApplied to the element for selection during styling or otherwise.
\n \n\nApplied to the element when a validation error exists on the field.
\n ",
+ "raw": "\n\nThe `Label` component must be used as a child of a [Control](/docs/components/control) component. It renders a `` element and includes the necessary attributes to associate it with the control.\n\n## Usage\n\nWhen using a `Label` inside a [Control](/docs/components/control), you don't need to worry about the `for` attribute. Formsnap handles that for you.\n\n```svelte {3}\n\n\t\n\t\t{#snippet children({ props })}\n\t\t\tName \n\t\t\t \n\t\t{/snippet}\n\t \n \n```\n\n## API Reference\n\n### Props\n\n\n\nA `$bindable` reference to the underlying `` element rendered by the `Label` component.\n\n \n\n\n\nIf provided, the `Label` component will not render an HTML element and will instead expect you to spread the snippet's `props` onto an element of your choosing.\n\nSee the [`child`](/docs/composition/child) snippet documentation for more information.\n\n \n\n\" name=\"...rest\">\n\nAny additional props provided to the `Label` component will be spread onto the underlying HTML element.\n\n \n\n## Data Attributes\n\nThe following data attributes are automatically applied to the element rendered by the `Label` component.\n\n\n\nApplied to the element for selection during styling or otherwise.\n\n \n\n\n\nApplied to the element when a validation error exists on the field.\n\n ",
+ "toc": [
+ {
+ "title": "Usage",
+ "url": "#usage",
+ "items": []
+ },
+ {
+ "title": "API Reference",
+ "url": "#api-reference",
+ "items": [
+ {
+ "title": "Props",
+ "url": "#props",
+ "items": []
+ }
+ ]
+ },
+ {
+ "title": "Data Attributes",
+ "url": "#data-attributes",
+ "items": []
+ }
+ ],
+ "section": "Components",
+ "slug": "components/label",
+ "slugFull": "/components/label"
+ },
+ {
+ "title": "Legend",
+ "description": "Provides a title for a group of related form controls.",
+ "path": "components/legend",
+ "content": "\nYou should always use the Legend
component in conjunction with the Fieldset component to provide a title for a group of related form controls. See the the Fieldset
component's documentation for more information on when to use a fieldset.
\nProps \nThe Legend
component renders a <legend>
element and accepts all props that a standard HTML <legend>
element would accept along with a few additional props:
\n\nA $bindable
reference to the underlying HTML element rendered by the Legend
component.
\n \n\nIf provided, the Legend
component will not render an HTML element and will instead expect you to spread the snippet's props
onto an element of your choosing.
\nSee the child
snippet documentation for more information.
\n \n\" name=\"...rest\">\nAny additional props provided to the Legend
component will be spread onto the underlying HTML element.
\n \nData Attributes \nThe following attributes are automatically applied to the element rendered by the Legend
component.
\n\nApplied to the element for selection during styling or otherwise.
\n \n\nApplied to the element when a validation error exists on the field.
\n ",
+ "raw": "\n\nYou should always use the `Legend` component in conjunction with the [Fieldset](/docs/components/fieldset) component to provide a title for a group of related form controls. See the the `Fieldset` component's [documentation](/docs/components/fieldset) for more information on when to use a fieldset.\n\n## Props\n\nThe `Legend` component renders a `` element and accepts all props that a standard HTML `` element would accept along with a few additional props:\n\n\n\nA `$bindable` reference to the underlying HTML element rendered by the `Legend` component.\n\n \n\n\n\nIf provided, the `Legend` component will not render an HTML element and will instead expect you to spread the snippet's `props` onto an element of your choosing.\n\nSee the [`child`](/docs/composition/child) snippet documentation for more information.\n\n \n\n\" name=\"...rest\">\n\nAny additional props provided to the `Legend` component will be spread onto the underlying HTML element.\n\n \n\n## Data Attributes\n\nThe following attributes are automatically applied to the element rendered by the `Legend` component.\n\n\n\nApplied to the element for selection during styling or otherwise.\n\n \n\n\n\nApplied to the element when a validation error exists on the field.\n\n ",
+ "toc": [
+ {
+ "title": "Props",
+ "url": "#props",
+ "items": []
+ },
+ {
+ "title": "Data Attributes",
+ "url": "#data-attributes",
+ "items": []
+ }
+ ],
+ "section": "Components",
+ "slug": "components/legend",
+ "slugFull": "/components/legend"
+ },
+ {
+ "title": "Bits UI Select",
+ "description": "How to use the Select component from Bits UI with Formsnap.",
+ "path": "recipes/bits-ui-select",
+ "content": "\nThe Select
component from Bits UI is a simple, yet powerful component for building a custom select menu. It powers the Select
component for shadcn-svelte , which is one of the most popular UI projects for Svelte. This recipe will demonstrate how to integrate that component with Formsnap.
\nSingle Select \nWe're going to build a \"languages\" select menu that allows the user to select a single language from a list of pre-defined options. We'll use a code to represent the language's value, and the language's name as the label.
\n\nDefine the Schema
\nHere's the schema we'll use for the form we'll build in this guide. We'll assume you know how to setup the load function and actions in the +page.server.ts
file.
\nimport { z } from \"zod\";\n\nexport const languages = {\n\ten: \"English\",\n\tes: \"Spanish\",\n\tfr: \"French\",\n\tde: \"German\",\n\tit: \"Italian\",\n\tpt: \"Portuguese\",\n\tru: \"Russian\",\n\tzh: \"Chinese\",\n\tja: \"Japanese\",\n\tko: \"Korean\",\n} as const;\n\ntype Language = keyof typeof languages;\n\nexport const schema = z.object({\n\tlanguage: z.enum(Object.keys(languages) as [Language, ...Language[]]).default(\"en\"),\n});\n
\nSetup the Form
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { Select } from \"bits-ui\";\n\timport { Field, Control, Label, FieldErrors } from \"formsnap\";\n\timport { schema, languages } from \"./schema.js\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\n\tconst { form: formData, enhance } = form;\n\n\tconst selectedLanguageLabel = $derived(\n\t\t$formData.language ? languages[$formData.language] : \"Select a language\"\n\t);\n</script>\n\n<form method=\"POST\" use:enhance>\n\t<Field {form} name=\"language\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<Label>Language</Label>\n\t\t\t\t<Select.Root type=\"single\" bind:value={$formData.language} name={props.name}>\n\t\t\t\t\t<Select.Trigger {...props}>\n\t\t\t\t\t\t{selectedLabel}\n\t\t\t\t\t</Select.Trigger>\n\t\t\t\t\t<Select.Content>\n\t\t\t\t\t\t{#each Object.entries(languages) as [value, label]}\n\t\t\t\t\t\t\t<Select.Item {value}>\n\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t</Select.Item>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t</Select.Content>\n\t\t\t\t</Select.Root>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<Description>The docs will be translated to your preferred language.</Description>\n\t\t<FieldErrors />\n\t</Field>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nWe apply the control props
to the Select.Trigger
component so that the label and other accessibility attributes are associated with it.
\nWe apply the props.name
to the Select.Root
component so a hidden input is rendered for the select.
\nFinished Product
\nThat's it! 🎉
\nWith some additional styles and structure, the form could look something like this:
\n\n \nMultiple Select \nThe <Select />
component also supports multiple selection. Here's how you can use it to build a multi-select form.
\n\nDefine the Schema
\nHere's the schema we'll use for the form we'll build in this guide. We'll assume you know how to setup the load function and actions in the +page.server.ts
file.
\nimport { z } from \"zod\";\n\nexport const colors = {\n\tblu: \"Blue\",\n\tred: \"Red\",\n\tgrn: \"Green\",\n\tylw: \"Yellow\",\n\tblk: \"Black\",\n} as const;\n\ntype Color = keyof typeof colors;\n\nexport const schema = z.object({\n\tcolors: z\n\t\t.array(z.enum(Object.keys(colors) as [Color, ...Color[]]))\n\t\t.min(1, \"Please select at least one color.\"),\n});\n
\nSetup the Form
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { Select } from \"bits-ui\";\n\timport { Field, Control, Label, FieldErrors } from \"formsnap\";\n\timport { schema, colors } from \"./schema.js\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\n\tconst { form: formData } = form;\n\n\tconst selectedColors = $derived(\n\t\t$formData.colors.length ? $formData.colors.map((c) => colors[c]).join(\",\") : \"Select colors\"\n\t);\n</script>\n\n<form method=\"POST\" use:form.enhance>\n\t<Field {form} name=\"colors\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<Label>Favorite colors</Label>\n\t\t\t\t<Select.Root type=\"multiple\" bind:value={$formData.colors} name={props.name}>\n\t\t\t\t\t<Select.Trigger {...props}>\n\t\t\t\t\t\t{selectedColors}\n\t\t\t\t\t</Select.Trigger>\n\t\t\t\t\t<Select.Content>\n\t\t\t\t\t\t{#each Object.entries(colors) as [value, label]}\n\t\t\t\t\t\t\t<Select.Item {value} {label} />\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t</Select.Content>\n\t\t\t\t</Select.Root>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<Description>We'll use these colors to customize your experience.</Description>\n\t\t<FieldErrors />\n\t</Field>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nWe apply the control props
to the Select.Trigger
component so that the label and other accessibility attributes are associated with it.
\nWe apply the props.name
to the Select.Root
component so a hidden input is rendered for the select.
\nFinished Product
\nThat's it! 🎉
\nWith some additional styles and structure, the form could look something like this:
\n\n ",
+ "raw": "\n\nThe `Select` component from [Bits UI](https://bits-ui.com/docs/components/select) is a simple, yet powerful component for building a custom select menu. It powers the `Select` component for [shadcn-svelte](https://shadcn-svelte.com/docs/components/select), which is one of the most popular UI projects for Svelte. This recipe will demonstrate how to integrate that component with Formsnap.\n\n## Single Select\n\nWe're going to build a \"languages\" select menu that allows the user to select a single language from a list of pre-defined options. We'll use a code to represent the language's value, and the language's name as the label.\n\n\n\nDefine the Schema \n\nHere's the schema we'll use for the form we'll build in this guide. We'll assume you know how to setup the load function and actions in the `+page.server.ts` file.\n\n```ts title=\"schema.ts\"\nimport { z } from \"zod\";\n\nexport const languages = {\n\ten: \"English\",\n\tes: \"Spanish\",\n\tfr: \"French\",\n\tde: \"German\",\n\tit: \"Italian\",\n\tpt: \"Portuguese\",\n\tru: \"Russian\",\n\tzh: \"Chinese\",\n\tja: \"Japanese\",\n\tko: \"Korean\",\n} as const;\n\ntype Language = keyof typeof languages;\n\nexport const schema = z.object({\n\tlanguage: z.enum(Object.keys(languages) as [Language, ...Language[]]).default(\"en\"),\n});\n```\n\nSetup the Form \n\n```svelte title=\"+page.svelte\"\n\n\n\n\t\n\t\t\n\t\t\t{#snippet children({ props })}\n\t\t\t\tLanguage \n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{selectedLabel}\n\t\t\t\t\t \n\t\t\t\t\t\n\t\t\t\t\t\t{#each Object.entries(languages) as [value, label]}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t \n\t\t\t\t\t\t{/each}\n\t\t\t\t\t \n\t\t\t\t \n\t\t\t{/snippet}\n\t\t \n\t\tThe docs will be translated to your preferred language. \n\t\t \n\t \n\tSubmit \n \n```\n\nWe apply the control `props` to the `Select.Trigger` component so that the label and other accessibility attributes are associated with it.\n\nWe apply the `props.name` to the `Select.Root` component so a hidden input is rendered for the select.\n\nFinished Product \n\nThat's it! 🎉\n\nWith some additional styles and structure, the form could look something like this:\n\n \n\n \n\n## Multiple Select\n\nThe ` ` component also supports multiple selection. Here's how you can use it to build a multi-select form.\n\n\n\nDefine the Schema \n\nHere's the schema we'll use for the form we'll build in this guide. We'll assume you know how to setup the load function and actions in the `+page.server.ts` file.\n\n```ts title=\"schema.ts\"\nimport { z } from \"zod\";\n\nexport const colors = {\n\tblu: \"Blue\",\n\tred: \"Red\",\n\tgrn: \"Green\",\n\tylw: \"Yellow\",\n\tblk: \"Black\",\n} as const;\n\ntype Color = keyof typeof colors;\n\nexport const schema = z.object({\n\tcolors: z\n\t\t.array(z.enum(Object.keys(colors) as [Color, ...Color[]]))\n\t\t.min(1, \"Please select at least one color.\"),\n});\n```\n\nSetup the Form \n\n```svelte title=\"+page.svelte\"\n\n\n\n\t\n\t\t\n\t\t\t{#snippet children({ props })}\n\t\t\t\tFavorite colors \n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{selectedColors}\n\t\t\t\t\t \n\t\t\t\t\t\n\t\t\t\t\t\t{#each Object.entries(colors) as [value, label]}\n\t\t\t\t\t\t\t \n\t\t\t\t\t\t{/each}\n\t\t\t\t\t \n\t\t\t\t \n\t\t\t{/snippet}\n\t\t \n\t\tWe'll use these colors to customize your experience. \n\t\t \n\t \n\tSubmit \n \n```\n\nWe apply the control `props` to the `Select.Trigger` component so that the label and other accessibility attributes are associated with it.\n\nWe apply the `props.name` to the `Select.Root` component so a hidden input is rendered for the select.\n\nFinished Product \n\nThat's it! 🎉\n\nWith some additional styles and structure, the form could look something like this:\n\n \n\n ",
+ "toc": [
+ {
+ "title": "Single Select",
+ "url": "#single-select",
+ "items": []
+ },
+ {
+ "title": "Multiple Select",
+ "url": "#multiple-select",
+ "items": []
+ }
+ ],
+ "section": "Recipes",
+ "slug": "recipes/bits-ui-select",
+ "slugFull": "/recipes/bits-ui-select"
+ },
+ {
+ "title": "Checkbox Groups",
+ "description": "Learn how to build checkbox group inputs with Formsnap.",
+ "path": "recipes/checkbox-groups",
+ "content": "\nCheckbox groups are a set of checkboxes that allow users to select multiple options from a list, and are quite common in forms.
\nIn this guide, you'll learn how to build a checkbox group with Formsnap by building an \"Allergies\" checkbox group, where a user must select any food allergies they have. We'll start with very basic functionality and then look at more advanced refinements for validation.
\nCreate a Checkbox Group \nFor the purposes of this guide, we'll assume you're using the zod
and zodClient
adapters from Superforms , but the same principles apply to all adapters.
\n\nDefine the Schema
\nLet's start by defining a schema that contains an array
to hold the selected options. We'll create this inside the context=\"module\"
script tag of our Svelte component so we can access it in our component and +page.server.ts
file.
\n<script lang=\"ts\" context=\"module\">\n\timport { z } from \"zod\";\n\n\tconst allergies = [\"None\", \"Peanuts\", \"Shellfish\", \"Lactose\", \"Gluten\"] as const;\n\n\texport const schema = z.object({\n\t\tallergies: z\n\t\t\t.array(z.enum(allergies))\n\t\t\t.min(1, \"If you don't have any allergies, select 'None'.\"),\n\t});\n</script>\n
\nWe've defined an array named allergies
that holds the possible enum values, and then created a schema that requires at least one option to be selected.
\nSetup the Load Function & Actions
\nNext, we'll create a +page.server.ts
file where we'll define our load
function and actions
to handle the form submission.
\nimport { superValidate } from \"sveltekit-superforms\";\nimport type { Actions, PageServerLoad } from \"./$types\";\nimport { schema } from \"./+page.svelte\";\nimport { zod } from \"sveltekit-superforms/adapters\";\nimport { fail } from \"@sveltejs/kit\";\n\nexport const load: PageServerLoad = async () => {\n\treturn {\n\t\tform: await superValidate(zod(schema)),\n\t};\n};\n\nexport const actions: Actions = {\n\tdefault: async (event) => {\n\t\tconst form = await superValidate(event, zod(schema));\n\n\t\tif (!form.valid) {\n\t\t\treturn fail(400, { form });\n\t\t}\n\n\t\treturn { form };\n\t},\n};\n
\nNotice we're importing that schema we defined in our +page.svelte
file and using it to validate the form data in the load
function and actions
.
\nInitialize the SuperForm
\nNow that we have our schema defined and our load
function and actions
set up, we can initialize the SuperForm in our Svelte component.
\n<!-- script context=\"module\" tag -->\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport SuperDebug from \"sveltekit-superforms\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\tconst { form: formData } = form;\n</script>\n
\nWe're using the superForm
function to initialize the form, passing in the form
object from our load
function and the zodClient
adapter to handle client-side validation.
\nImport Components and Enhance the Form
\nNow that our SuperForm is initialized, we can use it to construct our checkbox group.
\nWe'll first import the components we'll need from Formsnap, and then setup a form
element with the enhance
action to progressively enhance the form with client-side validation.
\n<!-- script context=\"module\" tag -->\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { Fieldset, Legend, Label, Control, FieldErrors, Description } from \"formsnap\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\tconst { form: formData } = form;\n</script>\n\n<form method=\"POST\" use:form.enhance>\n\t<!-- ... -->\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nConstruct the Fieldset
\nSince each checkbox in the group is related to a single field, we'll use a Fieldset
component with a Legend
to group them together. We'll use the Description
component to provide more context about the fieldset and the FieldErrors
component to display validation errors.
\n<!-- script tags -->\n<form method=\"POST\" use:form.enhance>\n\t<Fieldset {form} name=\"allergies\">\n\t\t<Legend>Select your allergies</Legend>\n\t\t<!-- ... -->\n\t\t<Description>We'll accommodate your dietary restrictions.</Description>\n\t\t<FieldErrors />\n\t</Fieldset>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nNext, we'll iterate over the allergies
array and create a Control that includes a Label and a checkbox input for each option.
\n<!-- script tags -->\n<form method=\"POST\" use:form.enhance>\n\t<Fieldset {form} name=\"allergies\">\n\t\t<Legend>Select your allergies</Legend>\n\t\t{#each allergies as allergy}\n\t\t\t<Control>\n\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t<input\n\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t{...props}\n\t\t\t\t\t\tbind:group={$formData.allergies}\n\t\t\t\t\t\tvalue={allergy}\n\t\t\t\t\t/>\n\t\t\t\t\t<Label>{value}</Label>\n\t\t\t\t{/snippet}\n\t\t\t</Control>\n\t\t{/each}\n\t\t<Description>We'll accommodate your dietary restrictions.</Description>\n\t\t<FieldErrors />\n\t</Fieldset>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nImprove Validation
\nWe now have a functional checkbox group that allows users to select multiple options from a list. However, we can make some improvements to enhance the user experience and provide better validation feedback.
\nYou may have noticed that users can select \"None\"
and another allergy at the same time, which doesn't make sense. We can address this by adding a refinement to our schema to ensure that if \"None\"
is selected, no other allergies can be selected.
\n<script lang=\"ts\" context=\"module\">\n\timport { z } from \"zod\";\n\n\tconst allergies = [\"None\", \"Peanuts\", \"Shellfish\", \"Lactose\", \"Gluten\"] as const;\n\n\texport const schema = z.object({\n\t\tallergies: z\n\t\t\t.array(z.enum(allergies))\n\t\t\t.min(1, \"If you don't have any allergies, select 'None'.\")\n\t\t\t.refine((v) => {\n\t\t\t\treturn v.includes(\"None\") ? v.length === 1 : true;\n\t\t\t}, \"If you select 'None', you can't select any other allergies.\"),\n\t});\n</script>\n\n<!-- ...rest -->\n
\nWe've added a refine
method to the allergies
array to ensure that if \"None\"
is selected, no other allergies can be selected. If the user selects \"None\"
, the array length must be 1
, otherwise the validation will fail and the custom error message will be displayed.
\nFinished Product
\nThat's it! You've successfully created a checkbox group with Formsnap. With some custom styles and components applied, the finished product might look something like this:
\n\n \nTLDR - Show Me the Code \nFor those who prefer to skip the guide and get straight to the code, here's the code required to create a checkbox group with Formsnap.
\nimport { superValidate } from \"sveltekit-superforms\";\nimport type { Actions, PageServerLoad } from \"./$types\";\nimport { schema } from \"./+page.svelte\";\nimport { zod } from \"sveltekit-superforms/adapters\";\nimport { fail } from \"@sveltejs/kit\";\n\nexport const load: PageServerLoad = async () => {\n\treturn {\n\t\tform: await superValidate(zod(schema)),\n\t};\n};\n\nexport const actions: Actions = {\n\tdefault: async (event) => {\n\t\tconst form = await superValidate(event, zod(schema));\n\n\t\tif (!form.valid) {\n\t\t\treturn fail(400, { form });\n\t\t}\n\n\t\treturn { form };\n\t},\n};\n
\n<script lang=\"ts\" context=\"module\">\n\timport { z } from \"zod\";\n\n\texport const allergies = [\"None\", \"Peanuts\", \"Shellfish\", \"Lactose\", \"Gluten\"] as const;\n\n\texport const schema = z.object({\n\t\tallergies: z\n\t\t\t.array(z.enum(allergies))\n\t\t\t.min(1, \"If you don't have any allergies, select 'None'.\")\n\t\t\t.refine((v) => {\n\t\t\t\treturn v.includes(\"None\") ? v.length === 1 : true;\n\t\t\t}, \"If you select 'None', you can't select any other allergies.\"),\n\t});\n</script>\n\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { Fieldset, Legend, Label, Control, FieldErrors, Description } from \"formsnap\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data, {\n\t\tvalidators: zodClient(schema),\n\t});\n\tconst { form: formData } = form;\n</script>\n\n<form method=\"POST\" use:form.enhance>\n\t<Fieldset {form} name=\"allergies\">\n\t\t<Legend>Select any allergies you may have</Legend>\n\t\t{#each allergies as allergy}\n\t\t\t<Control>\n\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t<input\n\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t{...props}\n\t\t\t\t\t\tbind:group={$formData.allergies}\n\t\t\t\t\t\tvalue={allergy}\n\t\t\t\t\t/>\n\t\t\t\t\t<Label>{allergy}</Label>\n\t\t\t\t{/snippet}\n\t\t\t</Control>\n\t\t{/each}\n\t\t<Description>We'll accommodate your dietary restrictions.</Description>\n\t\t<FieldErrors />\n\t</Fieldset>\n\t<button type=\"submit\">Submit</button>\n</form>\n
",
+ "raw": "\n\nCheckbox groups are a set of checkboxes that allow users to select multiple options from a list, and are quite common in forms.\n\nIn this guide, you'll learn how to build a checkbox group with Formsnap by building an \"Allergies\" checkbox group, where a user must select any food allergies they have. We'll start with very basic functionality and then look at more advanced refinements for validation.\n\n## Create a Checkbox Group\n\nFor the purposes of this guide, we'll assume you're using the `zod` and `zodClient` adapters from [Superforms](https://superforms.rocks), but the same principles apply to all adapters.\n\n\n\nDefine the Schema \n\nLet's start by defining a schema that contains an `array` to hold the selected options. We'll create this inside the `context=\"module\"` script tag of our Svelte component so we can access it in our component and `+page.server.ts` file.\n\n```svelte title=\"+page.svelte\"\n\n```\n\nWe've defined an array named `allergies` that holds the possible enum values, and then created a schema that requires at least one option to be selected.\n\nSetup the Load Function & Actions \n\nNext, we'll create a `+page.server.ts` file where we'll define our `load` function and `actions` to handle the form submission.\n\n```ts title=\"+page.server.ts\"\nimport { superValidate } from \"sveltekit-superforms\";\nimport type { Actions, PageServerLoad } from \"./$types\";\nimport { schema } from \"./+page.svelte\";\nimport { zod } from \"sveltekit-superforms/adapters\";\nimport { fail } from \"@sveltejs/kit\";\n\nexport const load: PageServerLoad = async () => {\n\treturn {\n\t\tform: await superValidate(zod(schema)),\n\t};\n};\n\nexport const actions: Actions = {\n\tdefault: async (event) => {\n\t\tconst form = await superValidate(event, zod(schema));\n\n\t\tif (!form.valid) {\n\t\t\treturn fail(400, { form });\n\t\t}\n\n\t\treturn { form };\n\t},\n};\n```\n\nNotice we're importing that schema we defined in our `+page.svelte` file and using it to validate the form data in the `load` function and `actions`.\n\nInitialize the SuperForm \n\nNow that we have our schema defined and our `load` function and `actions` set up, we can initialize the SuperForm in our Svelte component.\n\n```svelte title=\"+page.svelte\"\n\n\n```\n\nWe're using the `superForm` function to initialize the form, passing in the `form` object from our `load` function and the `zodClient` adapter to handle client-side validation.\n\nImport Components and Enhance the Form \n\nNow that our SuperForm is initialized, we can use it to construct our checkbox group.\n\nWe'll first import the components we'll need from Formsnap, and then setup a `form` element with the `enhance` action to progressively enhance the form with client-side validation.\n\n```svelte title=\"+page.svelte\" {5,15-18}\n\n\n\n\n\t\n\tSubmit \n \n```\n\nConstruct the Fieldset \n\nSince each checkbox in the group is related to a single field, we'll use a `Fieldset` component with a `Legend` to group them together. We'll use the `Description` component to provide more context about the fieldset and the `FieldErrors` component to display validation errors.\n\n```svelte {3-8}\n\n\n\t\n\t\tSelect your allergies \n\t\t\n\t\tWe'll accommodate your dietary restrictions. \n\t\t \n\t \n\tSubmit \n \n```\n\nNext, we'll iterate over the `allergies` array and create a [Control](/docs/components/control) that includes a [Label](/docs/components/label) and a checkbox input for each option.\n\n```svelte {5-17}\n\n\n\t\n\t\tSelect your allergies \n\t\t{#each allergies as allergy}\n\t\t\t\n\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t \n\t\t\t\t\t{value} \n\t\t\t\t{/snippet}\n\t\t\t \n\t\t{/each}\n\t\tWe'll accommodate your dietary restrictions. \n\t\t \n\t \n\tSubmit \n \n```\n\nImprove Validation \n\nWe now have a functional checkbox group that allows users to select multiple options from a list. However, we can make some improvements to enhance the user experience and provide better validation feedback.\n\nYou may have noticed that users can select `\"None\"` and another allergy at the same time, which doesn't make sense. We can address this by adding a refinement to our schema to ensure that if `\"None\"` is selected, no other allergies can be selected.\n\n```svelte title=\"+page.svelte\" {10-12}\n\n\n\n```\n\nWe've added a `refine` method to the `allergies` array to ensure that if `\"None\"` is selected, no other allergies can be selected. If the user selects `\"None\"`, the array length must be `1`, otherwise the validation will fail and the custom error message will be displayed.\n\nFinished Product \n\nThat's it! You've successfully created a checkbox group with Formsnap. With some custom styles and components applied, the finished product might look something like this:\n\n \n\n \n\n## TLDR - Show Me the Code\n\nFor those who prefer to skip the guide and get straight to the code, here's the code required to create a checkbox group with Formsnap.\n\n```ts title=\"+page.server.ts\"\nimport { superValidate } from \"sveltekit-superforms\";\nimport type { Actions, PageServerLoad } from \"./$types\";\nimport { schema } from \"./+page.svelte\";\nimport { zod } from \"sveltekit-superforms/adapters\";\nimport { fail } from \"@sveltejs/kit\";\n\nexport const load: PageServerLoad = async () => {\n\treturn {\n\t\tform: await superValidate(zod(schema)),\n\t};\n};\n\nexport const actions: Actions = {\n\tdefault: async (event) => {\n\t\tconst form = await superValidate(event, zod(schema));\n\n\t\tif (!form.valid) {\n\t\t\treturn fail(400, { form });\n\t\t}\n\n\t\treturn { form };\n\t},\n};\n```\n\n```svelte title=\"+page.svelte\"\n\n\n\n\n\n\t\n\t\tSelect any allergies you may have \n\t\t{#each allergies as allergy}\n\t\t\t\n\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t \n\t\t\t\t\t{allergy} \n\t\t\t\t{/snippet}\n\t\t\t \n\t\t{/each}\n\t\tWe'll accommodate your dietary restrictions. \n\t\t \n\t \n\tSubmit \n \n```",
+ "toc": [
+ {
+ "title": "Create a Checkbox Group",
+ "url": "#create-a-checkbox-group",
+ "items": []
+ },
+ {
+ "title": "TLDR - Show Me the Code",
+ "url": "#tldr---show-me-the-code",
+ "items": []
+ }
+ ],
+ "section": "Recipes",
+ "slug": "recipes/checkbox-groups",
+ "slugFull": "/recipes/checkbox-groups"
+ },
+ {
+ "title": "Dynamic Fields",
+ "description": "Learn how to creating dynamic fields by building a URLs field with Formsnap.",
+ "path": "recipes/dynamic-fields",
+ "content": "\nTo create a dynamic field, you'll need to use the ElementField component, that allows you to treat each element of an array as it's own field.
\nIn this recipe, we'll create a URLs field where users can add and remove URLs from their profile.
\nCreate Dynamic Fields \n\nDefine the Schema
\nHere's the schema we'll use for the form we'll build in this guide. We'll assume you know how to setup the load function and actions in the +page.server.ts
file.
\nimport { z } from \"zod\";\n\nexport const schema = z.object({\n\turls: z\n\t\t.array(z.string().url({ message: \"Please enter a valid URL.\" }))\n\t\t.min(2, \"You must include at least two URLs on your profile.\")\n\t\t.default([\"\", \"\"]),\n});\n
\nWe've defined an array named urls
that contains strings that must be valid URLs. We've also set a minimum length of 2 for the array itself, and provided two default values to start with. The minimum length of 2 may sounds strange, but we're only doing so to demonstrate different validation errors for the array and its elements.
\nCreate the Form
\nWe'll need to initialize our SuperForm with the form returned from the load
function, and then setup the basic structure of our form.
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { schema } from \"./schema.js\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\n\tconst { form: formData } = form;\n</script>\n\n<form use:form.enhance method=\"POST\">\n\t<!-- ... -->\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nImport the Components
\nWe have a few components we need to import to build the form.
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport {\n\t\tFieldset,\n\t\tLegend,\n\t\tElementField,\n\t\tControl,\n\t\tLabel,\n\t\tFieldErrors,\n\t\tDescription,\n\t} from \"formsnap\";\n\timport { schema } from \"./schema.js\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\n\tconst { form: formData } = form;\n</script>\n\n<form use:form.enhance method=\"POST\">\n\t<!-- ... -->\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nScaffold the Fieldset
\nSince our individual URL inputs will be part of the same field, we'll use a Fieldset component to group them together and a Legend to provide a title.
\n<!-- script tag -->\n<form use:form.enhance method=\"POST\">\n\t<Fieldset {form} name=\"urls\">\n\t\t<Legend>Public URLs</Legend>\n\t\t<!-- ... -->\n\t\t<Description>Add URLs to your profile that you'd like to share with others.</Description>\n\t\t<FieldErrors />\n\t</Fieldset>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nThe FieldErrors component will display any validation errors for the array itself. In our case, it will display an error if the array doesn't contain at least two URLs (we'll add the errors for the individual URLs in the next step).
\nThe Description component will provide additional context about the fields once we've created them, but each field will share the same description from the Fieldset scope.
\nRender the URL Fields
\nNow that we've scaffolded the Fieldset
, we can iterate over the $formData.urls
array to render the individual URL fields, which are represented by the ElementField component.
\n<!-- script tag -->\n<form use:enhance method=\"POST\">\n\t<Fieldset {form} name=\"urls\">\n\t\t<Legend>Public URLs</Legend>\n\t\t{#each $formData.urls as _, i}\n\t\t\t<ElementField {form} name=\"urls[{i}]\">\n\t\t\t\t<Control>\n\t\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t\t<Label class=\"sr-only\">URL {i + 1}</Label>\n\t\t\t\t\t\t<input type=\"url\" {...props} bind:value={$formData.urls[i]} />\n\t\t\t\t\t{/snippet}\n\t\t\t\t</Control>\n\t\t\t\t<Description class=\"sr-only\">\n\t\t\t\t\tThis URL will be publicly available on your profile.\n\t\t\t\t</Description>\n\t\t\t\t<FieldErrors />\n\t\t\t</ElementField>\n\t\t{/each}\n\t\t<FieldErrors />\n\t</Fieldset>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nWe're using the ElementField
component to treat each element of the urls
array as a separate field with its own state and validation. We're also using the Control
component to create a label and input for each URL, and binding the input's value to the corresponding element of the urls
array.
\n\nYou should always include a label for each input for accessibility purposes. In this case, because we don't want to display a label visually for each input, we've added a class to the label to visually hide it while still making it available to screen readers.
\n \nMake the Fields Dynamic
\nAt the moment, the user can only have two URLs in their profile. We want to allow them to add and remove URLs as needed. We can achieve this by adding buttons to add and remove URLs.
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport {\n\t\tFieldset,\n\t\tLegend,\n\t\tElementField,\n\t\tControl,\n\t\tLabel,\n\t\tFieldErrors,\n\t\tDescription,\n\t} from \"formsnap\";\n\timport { schema } from \"./schema.js\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\n\tconst { form: formData } = form;\n\n\tfunction removeUrlByIndex(index: number) {\n\t\t$formData.urls = $formData.urls.filter((_, i) => i !== index);\n\t}\n\n\tfunction addUrl() {\n\t\t$formData.urls = [...$formData.urls, \"\"];\n\t}\n</script>\n\n<form use:form.enhance method=\"POST\">\n\t<Fieldset {form} name=\"urls\">\n\t\t<Legend>Public URLs</Legend>\n\t\t{#each $formData.urls as _, i}\n\t\t\t<ElementField {form} name=\"urls[{i}]\">\n\t\t\t\t<Control>\n\t\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t\t<Label class=\"sr-only\">URL {i + 1}</Label>\n\t\t\t\t\t\t<input type=\"url\" {...props} bind:value={$formData.urls[i]} />\n\t\t\t\t\t\t<button type=\"button\" onclick={() => removeUrlByIndex(i)}>\n\t\t\t\t\t\t\tRemove URL\n\t\t\t\t\t\t</button>\n\t\t\t\t\t{/snippet}\n\t\t\t\t</Control>\n\t\t\t\t<Description class=\"sr-only\">\n\t\t\t\t\tThis URL will be publicly available on your profile.\n\t\t\t\t</Description>\n\t\t\t\t<FieldErrors />\n\t\t\t</ElementField>\n\t\t{/each}\n\t\t<FieldErrors />\n\t\t<button type=\"button\" onclick={addUrl}>Add URL</button>\n\t</Fieldset>\n\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nWe've added a removeUrlByIndex
function that removes a URL from the urls
array by its index, and a addUrl
function that adds a new URL to the urls
array. We've also added a button to remove each URL and a button to add a new URL.
\nNow the user can add and remove URLs as needed, and the form will validate the array and its elements according to the schema we defined.
\nFinished Product
\nThat's it! 🎉
\nYou've created a dynamic field that allows users to add and remove URLs from their profile. With some custom styles and finesse, you can make the form look something like this:
\n\n \nTLDR - Show Me the Code \nHere's the complete code for the form we built in this guide:
\nimport { z } from \"zod\";\n\nexport const schema = z.object({\n\turls: z\n\t\t.array(z.string().url({ message: \"Please enter a valid URL.\" }))\n\t\t.min(2, \"You must include at least two URLs on your profile.\")\n\t\t.default([\"\", \"\"]),\n});\n
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport {\n\t\tFieldset,\n\t\tLegend,\n\t\tElementField,\n\t\tControl,\n\t\tLabel,\n\t\tFieldErrors,\n\t\tDescription,\n\t} from \"formsnap\";\n\timport { schema } from \"./schema.js\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\n\tconst { form: formData } = form;\n\n\tfunction removeUrlByIndex(index: number) {\n\t\t$formData.urls = $formData.urls.filter((_, i) => i !== index);\n\t}\n\n\tfunction addUrl() {\n\t\t$formData.urls = [...$formData.urls, \"\"];\n\t}\n</script>\n\n<form use:form.enhance method=\"POST\">\n\t<Fieldset {form} name=\"urls\">\n\t\t<Legend>Public URLs</Legend>\n\t\t{#each $formData.urls as _, i}\n\t\t\t<ElementField {form} name=\"urls[{i}]\">\n\t\t\t\t<Control>\n\t\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t\t<Label class=\"sr-only\">URL {i + 1}</Label>\n\t\t\t\t\t\t<input type=\"url\" {...props} bind:value={$formData.urls[i]} />\n\t\t\t\t\t\t<button type=\"button\" onclick={() => removeUrlByIndex(i)}>\n\t\t\t\t\t\t\tRemove URL\n\t\t\t\t\t\t</button>\n\t\t\t\t\t{/snippet}\n\t\t\t\t</Control>\n\t\t\t\t<Description class=\"sr-only\">\n\t\t\t\t\tThis URL will be publicly available on your profile.\n\t\t\t\t</Description>\n\t\t\t</ElementField>\n\t\t{/each}\n\t\t<FieldErrors />\n\t\t<button type=\"button\" onclick={addUrl}>Add URL</button>\n\t</Fieldset>\n\n\t<button type=\"submit\">Submit</button>\n</form>\n
",
+ "raw": "\n\nTo create a dynamic field, you'll need to use the [ElementField](/docs/components/element-field) component, that allows you to treat each element of an array as it's own field.\n\nIn this recipe, we'll create a URLs field where users can add and remove URLs from their profile.\n\n## Create Dynamic Fields\n\n\n\nDefine the Schema \n\nHere's the schema we'll use for the form we'll build in this guide. We'll assume you know how to setup the load function and actions in the `+page.server.ts` file.\n\n```ts title=\"schema.ts\"\nimport { z } from \"zod\";\n\nexport const schema = z.object({\n\turls: z\n\t\t.array(z.string().url({ message: \"Please enter a valid URL.\" }))\n\t\t.min(2, \"You must include at least two URLs on your profile.\")\n\t\t.default([\"\", \"\"]),\n});\n```\n\nWe've defined an array named `urls` that contains strings that must be valid URLs. We've also set a minimum length of 2 for the array itself, and provided two default values to start with. The minimum length of 2 may sounds strange, but we're only doing so to demonstrate different validation errors for the array and its elements.\n\nCreate the Form \n\nWe'll need to initialize our SuperForm with the form returned from the `load` function, and then setup the basic structure of our form.\n\n```svelte title=\"+page.svelte\"\n\n\n\n\t\n\tSubmit \n \n```\n\nImport the Components \n\nWe have a few components we need to import to build the form.\n\n```svelte title=\"+page.svelte\" {4-12}\n\n\n\n\t\n\tSubmit \n \n```\n\nScaffold the Fieldset \n\nSince our individual URL inputs will be part of the same field, we'll use a [Fieldset](/docs/components/fieldset) component to group them together and a [Legend](/docs/components/legend) to provide a title.\n\n```svelte title=\"+page.svelte\" {3-8}\n\n\n\t\n\t\tPublic URLs \n\t\t\n\t\tAdd URLs to your profile that you'd like to share with others. \n\t\t \n\t \n\tSubmit \n \n```\n\nThe [FieldErrors](/docs/components/field-errors) component will display any validation errors for the array itself. In our case, it will display an error if the array doesn't contain at least two URLs (we'll add the errors for the individual URLs in the next step).\n\nThe [Description](/docs/components/description) component will provide additional context about the fields once we've created them, but each field will share the same description from the [Fieldset](/docs/components/fieldset) scope.\n\nRender the URL Fields \n\nNow that we've scaffolded the `Fieldset`, we can iterate over the `$formData.urls` array to render the individual URL fields, which are represented by the [ElementField](/docs/components/element-field) component.\n\n```svelte title=\"+page.svelte\" {5-18}\n\n\n\t\n\t\tPublic URLs \n\t\t{#each $formData.urls as _, i}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t\tURL {i + 1} \n\t\t\t\t\t\t \n\t\t\t\t\t{/snippet}\n\t\t\t\t \n\t\t\t\t\n\t\t\t\t\tThis URL will be publicly available on your profile.\n\t\t\t\t \n\t\t\t\t \n\t\t\t \n\t\t{/each}\n\t\t \n\t \n\tSubmit \n \n```\n\nWe're using the `ElementField` component to treat each element of the `urls` array as a separate field with its own state and validation. We're also using the `Control` component to create a label and input for each URL, and binding the input's value to the corresponding element of the `urls` array.\n\n\n\nYou should always include a label for each input for accessibility purposes. In this case, because we don't want to display a label visually for each input, we've added a class to the label to visually hide it while still making it available to screen readers.\n\n \n\nMake the Fields Dynamic \n\nAt the moment, the user can only have two URLs in their profile. We want to allow them to add and remove URLs as needed. We can achieve this by adding buttons to add and remove URLs.\n\n```svelte showLineNumbers title=\"+page.svelte\" {23-29,41-43,53}\n\n\n\n\t\n\t\tPublic URLs \n\t\t{#each $formData.urls as _, i}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t\tURL {i + 1} \n\t\t\t\t\t\t \n\t\t\t\t\t\t removeUrlByIndex(i)}>\n\t\t\t\t\t\t\tRemove URL\n\t\t\t\t\t\t \n\t\t\t\t\t{/snippet}\n\t\t\t\t \n\t\t\t\t\n\t\t\t\t\tThis URL will be publicly available on your profile.\n\t\t\t\t \n\t\t\t\t \n\t\t\t \n\t\t{/each}\n\t\t \n\t\tAdd URL \n\t \n\n\tSubmit \n \n```\n\nWe've added a `removeUrlByIndex` function that removes a URL from the `urls` array by its index, and a `addUrl` function that adds a new URL to the `urls` array. We've also added a button to remove each URL and a button to add a new URL.\n\nNow the user can add and remove URLs as needed, and the form will validate the array and its elements according to the schema we defined.\n\nFinished Product \n\nThat's it! 🎉\n\nYou've created a dynamic field that allows users to add and remove URLs from their profile. With some custom styles and finesse, you can make the form look something like this:\n\n \n\n \n\n## TLDR - Show Me the Code\n\nHere's the complete code for the form we built in this guide:\n\n```ts title=\"schema.ts\"\nimport { z } from \"zod\";\n\nexport const schema = z.object({\n\turls: z\n\t\t.array(z.string().url({ message: \"Please enter a valid URL.\" }))\n\t\t.min(2, \"You must include at least two URLs on your profile.\")\n\t\t.default([\"\", \"\"]),\n});\n```\n\n```svelte title=\"+page.svelte\"\n\n\n\n\t\n\t\tPublic URLs \n\t\t{#each $formData.urls as _, i}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t{#snippet children({ props })}\n\t\t\t\t\t\tURL {i + 1} \n\t\t\t\t\t\t \n\t\t\t\t\t\t removeUrlByIndex(i)}>\n\t\t\t\t\t\t\tRemove URL\n\t\t\t\t\t\t \n\t\t\t\t\t{/snippet}\n\t\t\t\t \n\t\t\t\t\n\t\t\t\t\tThis URL will be publicly available on your profile.\n\t\t\t\t \n\t\t\t \n\t\t{/each}\n\t\t \n\t\tAdd URL \n\t \n\n\tSubmit \n \n```",
+ "toc": [
+ {
+ "title": "Create Dynamic Fields",
+ "url": "#create-dynamic-fields",
+ "items": []
+ },
+ {
+ "title": "TLDR - Show Me the Code",
+ "url": "#tldr---show-me-the-code",
+ "items": []
+ }
+ ],
+ "section": "Recipes",
+ "slug": "recipes/dynamic-fields",
+ "slugFull": "/recipes/dynamic-fields"
+ },
+ {
+ "title": "Multiple Select",
+ "description": "Learn how to build multiple select inputs with Formsnap.",
+ "path": "recipes/multiple-select",
+ "content": "\nIn the following guide, you'll learn how to setup and validate multiple select fields with Formsnap by building an Ice Cream order form.
\nBuilding a Multiple Select Form \n\nDefine the Schema
\nHere'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.
\nimport { z } from \"zod\";\n\nexport const flavors = [\"vanilla\", \"chocolate\", \"cookies and cream\", \"strawberry\"] as const;\n\nexport const toppings = [\"sprinkles\", \"hot fudge\", \"whipped cream\", \"cherry\"] as const;\n\nexport const schema = z\n\t.object({\n\t\tscoops: z.number().min(1).default(1),\n\t\tflavors: z.array(z.enum(flavors)).min(1, \"You must select at least one flavor.\"),\n\t\ttoppings: z.array(z.enum(toppings)).max(2, \"You can only select up to two toppings.\"),\n\t})\n\t.refine((data) => (data.flavors.length > data.scoops ? false : true), {\n\t\tmessage: \"You can only select as many flavors as you have scoops.\",\n\t\tpath: [\"flavors\"],\n\t});\n
\nThe 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.
\nCreate the Form
\nLet'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.
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { schema, flavors, toppings } from \"./schema.js\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\n\tconst { form: formData } = form;\n</script>\n\n<form use:form.enhance method=\"POST\">\n\t<!-- ... -->\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nImport the Components
\nAt a minimum we need to import the Field , Control , Label , and FieldErrors components from Formsnap.
\n<script lang=\"ts\">\n\timport { superForm } from \"sveltekit-superforms\";\n\timport { zodClient } from \"sveltekit-superforms/adapters\";\n\timport { Field, Control, Label, FieldErrors } from \"formsnap\";\n\timport { schema } from \"./schema.js\";\n\n\tlet { data } = $props();\n\n\tconst form = superForm(data.form, {\n\t\tvalidators: zodClient(schema),\n\t});\n\n\tconst { form: formData } = form;\n</script>\n\n<form use:form.enhance method=\"POST\">\n\t<!-- ... -->\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nCreate the Scoops Field
\nThe first field we'll create is the scoops
field, which will be a regular select input with a range of 1 to 5 scoops.
\n<!-- script tag -->\n<form use:form.enhance method=\"POST\">\n\t<Field {form} name=\"scoops\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<div class=\"flex flex-col items-start gap-1\">\n\t\t\t\t\t<Label>Number of scoops</Label>\n\t\t\t\t\t<select {...props} bind:value={$formData.scoops}>\n\t\t\t\t\t\t{#each Array.from({ length: 5 }, (_, i) => i + 1) as num}\n\t\t\t\t\t\t\t<option value={num}>\n\t\t\t\t\t\t\t\t{num === 1 ? `${num} Scoop` : `${num} Scoops`}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<FieldErrors />\n\t</Field>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nCreate the Flavors Field
\nNext, let's create the flavors
field. This field will be a multiple select input with the available flavors as options.
\n<!-- script tag -->\n<form use:form.enhance method=\"POST\">\n\t<Field {form} name=\"scoops\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<div class=\"flex flex-col items-start gap-1\">\n\t\t\t\t\t<Label>Number of scoops</Label>\n\t\t\t\t\t<select {...props} bind:value={$formData.scoops}>\n\t\t\t\t\t\t{#each Array.from({ length: 5 }, (_, i) => i + 1) as num}\n\t\t\t\t\t\t\t<option value={num}>\n\t\t\t\t\t\t\t\t{num === 1 ? `${num} Scoop` : `${num} Scoops`}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<FieldErrors />\n\t</Field>\n\t<Field {form} name=\"flavors\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<div class=\"flex flex-col items-start gap-1\">\n\t\t\t\t\t<Label>What flavors do you fancy?</Label>\n\t\t\t\t\t<select multiple bind:value={$formData.flavors} {...props}>\n\t\t\t\t\t\t{#each flavors as flavor}\n\t\t\t\t\t\t\t<option value={flavor} selected={$formData.flavors.includes(flavor)}>\n\t\t\t\t\t\t\t\t{flavor}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<FieldErrors />\n\t</Field>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nNotice 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.
\nCreate the Toppings Field
\nFinally, let's create the toppings
field. This field will also be a multiple select input with the available toppings as options.
\n<!-- script tag -->\n<form use:form.enhance method=\"POST\">\n\t<Field {form} name=\"scoops\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<div class=\"flex flex-col items-start gap-1\">\n\t\t\t\t\t<Label>Number of scoops</Label>\n\t\t\t\t\t<select {...props} bind:value={$formData.scoops}>\n\t\t\t\t\t\t{#each Array.from({ length: 5 }, (_, i) => i + 1) as num}\n\t\t\t\t\t\t\t<option value={num}>\n\t\t\t\t\t\t\t\t{num === 1 ? `${num} Scoop` : `${num} Scoops`}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<FieldErrors />\n\t</Field>\n\t<Field {form} name=\"flavors\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<div class=\"flex flex-col items-start gap-1\">\n\t\t\t\t\t<Label>What flavors do you fancy?</Label>\n\t\t\t\t\t<select multiple bind:value={$formData.flavors} {...props}>\n\t\t\t\t\t\t{#each flavors as flavor}\n\t\t\t\t\t\t\t<option value={flavor} selected={$formData.flavors.includes(flavor)}>\n\t\t\t\t\t\t\t\t{flavor}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<FieldErrors />\n\t</Field>\n\t<Field {form} name=\"toppings\">\n\t\t<Control>\n\t\t\t{#snippet children({ props })}\n\t\t\t\t<div class=\"flex flex-col items-start gap-1\">\n\t\t\t\t\t<Label>Select your toppings</Label>\n\t\t\t\t\t<select multiple bind:value={$formData.toppings} {...props}>\n\t\t\t\t\t\t{#each toppings as topping}\n\t\t\t\t\t\t\t<option value={topping} selected={$formData.toppings.includes(topping)}>\n\t\t\t\t\t\t\t\t{topping}\n\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t</select>\n\t\t\t\t</div>\n\t\t\t{/snippet}\n\t\t</Control>\n\t\t<FieldErrors />\n\t</Field>\n\t<button type=\"submit\">Submit</button>\n</form>\n
\nFinished Product
\nThat's it! 🎉
\nYou'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:
\n\n ",
+ "raw": "\n\nIn the following guide, you'll learn how to setup and validate multiple select fields with Formsnap by building an Ice Cream order form.\n\n## Building a Multiple Select Form\n\n\n\nDefine the Schema \n\nHere'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.\n\n```ts title=\"schema.ts\"\nimport { z } from \"zod\";\n\nexport const flavors = [\"vanilla\", \"chocolate\", \"cookies and cream\", \"strawberry\"] as const;\n\nexport const toppings = [\"sprinkles\", \"hot fudge\", \"whipped cream\", \"cherry\"] as const;\n\nexport const schema = z\n\t.object({\n\t\tscoops: z.number().min(1).default(1),\n\t\tflavors: z.array(z.enum(flavors)).min(1, \"You must select at least one flavor.\"),\n\t\ttoppings: z.array(z.enum(toppings)).max(2, \"You can only select up to two toppings.\"),\n\t})\n\t.refine((data) => (data.flavors.length > data.scoops ? false : true), {\n\t\tmessage: \"You can only select as many flavors as you have scoops.\",\n\t\tpath: [\"flavors\"],\n\t});\n```\n\nThe 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.\n\nCreate the Form \n\nLet'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.\n\n```svelte title=\"+page.svelte\"\n\n\n\n\t\n\tSubmit \n \n```\n\nImport the Components \n\nAt a minimum we need to import the [Field](/docs/components/field), [Control](/docs/components/control), [Label](/docs/components/label), and [FieldErrors](/docs/components/field-errors) components from Formsnap.\n\n```svelte title=\"+page.svelte\" {4}\n\n\n\n\t\n\tSubmit \n \n```\n\nCreate the Scoops Field \n\nThe first field we'll create is the `scoops` field, which will be a regular select input with a range of 1 to 5 scoops.\n\n```svelte title=\"+page.svelte\" {3-19}\n\n\n\t\n\t\t\n\t\t\t{#snippet children({ props })}\n\t\t\t\t\n\t\t\t\t\tNumber of scoops \n\t\t\t\t\t\n\t\t\t\t\t\t{#each Array.from({ length: 5 }, (_, i) => i + 1) as num}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{num === 1 ? `${num} Scoop` : `${num} Scoops`}\n\t\t\t\t\t\t\t \n\t\t\t\t\t\t{/each}\n\t\t\t\t\t \n\t\t\t\t
\n\t\t\t{/snippet}\n\t\t \n\t\t \n\t \n\tSubmit \n \n```\n\nCreate the Flavors Field \n\nNext, let's create the `flavors` field. This field will be a multiple select input with the available flavors as options.\n\n```svelte title=\"+page.svelte\" {20-36}\n\n\n\t\n\t\t\n\t\t\t{#snippet children({ props })}\n\t\t\t\t\n\t\t\t\t\tNumber of scoops \n\t\t\t\t\t\n\t\t\t\t\t\t{#each Array.from({ length: 5 }, (_, i) => i + 1) as num}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{num === 1 ? `${num} Scoop` : `${num} Scoops`}\n\t\t\t\t\t\t\t \n\t\t\t\t\t\t{/each}\n\t\t\t\t\t \n\t\t\t\t
\n\t\t\t{/snippet}\n\t\t \n\t\t \n\t \n\t\n\t\t\n\t\t\t{#snippet children({ props })}\n\t\t\t\t\n\t\t\t\t\tWhat flavors do you fancy? \n\t\t\t\t\t\n\t\t\t\t\t\t{#each flavors as flavor}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{flavor}\n\t\t\t\t\t\t\t \n\t\t\t\t\t\t{/each}\n\t\t\t\t\t \n\t\t\t\t
\n\t\t\t{/snippet}\n\t\t \n\t\t \n\t \n\tSubmit \n \n```\n\nNotice 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.\n\nCreate the Toppings Field \n\nFinally, let's create the `toppings` field. This field will also be a multiple select input with the available toppings as options.\n\n```svelte title=\"+page.svelte\" {37-53}\n\n\n\t\n\t\t\n\t\t\t{#snippet children({ props })}\n\t\t\t\t\n\t\t\t\t\tNumber of scoops \n\t\t\t\t\t\n\t\t\t\t\t\t{#each Array.from({ length: 5 }, (_, i) => i + 1) as num}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{num === 1 ? `${num} Scoop` : `${num} Scoops`}\n\t\t\t\t\t\t\t \n\t\t\t\t\t\t{/each}\n\t\t\t\t\t \n\t\t\t\t
\n\t\t\t{/snippet}\n\t\t \n\t\t \n\t \n\t\n\t\t\n\t\t\t{#snippet children({ props })}\n\t\t\t\t\n\t\t\t\t\tWhat flavors do you fancy? \n\t\t\t\t\t\n\t\t\t\t\t\t{#each flavors as flavor}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{flavor}\n\t\t\t\t\t\t\t \n\t\t\t\t\t\t{/each}\n\t\t\t\t\t \n\t\t\t\t
\n\t\t\t{/snippet}\n\t\t \n\t\t \n\t \n\t\n\t\t\n\t\t\t{#snippet children({ props })}\n\t\t\t\t\n\t\t\t\t\tSelect your toppings \n\t\t\t\t\t\n\t\t\t\t\t\t{#each toppings as topping}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{topping}\n\t\t\t\t\t\t\t \n\t\t\t\t\t\t{/each}\n\t\t\t\t\t \n\t\t\t\t
\n\t\t\t{/snippet}\n\t\t \n\t\t \n\t \n\tSubmit \n \n```\n\nFinished Product \n\nThat's it! 🎉\n\nYou'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:\n\n \n\n ",
+ "toc": [
+ {
+ "title": "Building a Multiple Select Form",
+ "url": "#building-a-multiple-select-form",
+ "items": []
+ }
+ ],
+ "section": "Recipes",
+ "slug": "recipes/multiple-select",
+ "slugFull": "/recipes/multiple-select"
+ }
+]
diff --git a/docs/.velite/index.d.ts b/docs/.velite/index.d.ts
index 84ac88e..0828c7b 100644
--- a/docs/.velite/index.d.ts
+++ b/docs/.velite/index.d.ts
@@ -1,8 +1,8 @@
// This file is generated by Velite
-import type __vc from '../velite.config.js'
+import type __vc from "../velite.config.js";
-type Collections = typeof __vc.collections
+type Collections = typeof __vc.collections;
-export type Doc = Collections['docs']['schema']['_output']
-export declare const docs: Doc[]
+export type Doc = Collections["docs"]["schema"]["_output"];
+export declare const docs: Doc[];
diff --git a/docs/.velite/index.js b/docs/.velite/index.js
index 63f52f3..a4f78d4 100644
--- a/docs/.velite/index.js
+++ b/docs/.velite/index.js
@@ -1,3 +1,3 @@
// This file is generated by Velite
-export { default as docs } from './docs.json'
\ No newline at end of file
+export { default as docs } from "./docs.json";
diff --git a/docs/static/site.webmanifest b/docs/static/site.webmanifest
index 45dc8a2..52a2fe3 100644
--- a/docs/static/site.webmanifest
+++ b/docs/static/site.webmanifest
@@ -1 +1,11 @@
-{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
\ No newline at end of file
+{
+ "name": "",
+ "short_name": "",
+ "icons": [
+ { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
+ { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
+}