diff --git a/docs/.velite/docs.json b/docs/.velite/docs.json index aed4a35..720bbb8 100644 --- a/docs/.velite/docs.json +++ b/docs/.velite/docs.json @@ -36,8 +36,8 @@ "title": "Quick start", "description": "Learn how to take off with Formsnap by building a settings form.", "path": "quick-start", - "content": "\n
Since 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
\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.
\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:
\nemail
-> <input type=\"email\">
bio
-> <textarea>
theme
-> <input type=\"radio\">
language
-> <select>
marketingEmails
-> <input type=\"checkbox>
allergies
-> <input type=\"checkbox\">
(group/multiple)Of course, there are other ways to represent the data, but this is the approach we'll take for this tutorial.
\nIn Superforms fashion, we'll return the form from a load function to seamlessly merge our PageData
and ActionData
.
import 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
\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.
<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.
You 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.
<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.
Now 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.
The 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.
The 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.
And that's really all it takes to setup a form field. Let's continue on with the rest of the 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.
And that's it! You've now successfully built a settings form with Formsnap!
\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\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
\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.
\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:
\nemail
-> <input type=\"email\">
bio
-> <textarea>
theme
-> <input type=\"radio\">
language
-> <select>
marketingEmails
-> <input type=\"checkbox>
allergies
-> <input type=\"checkbox\">
(group/multiple)Of course, there are other ways to represent the data, but this is the approach we'll take for this tutorial.
\nIn Superforms fashion, we'll return the form from a load function to seamlessly merge our PageData
and ActionData
.
import 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
\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.
<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.
You 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.
<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<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.
Now 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.
The 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.
The 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.
And that's really all it takes to setup a form field. Let's continue on with the rest of the 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.
And that's it! You've now successfully built a settings form with Formsnap!
\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\nFormsnap v2 has been rewritten to support Svelte 5 and with that, comes some breaking changes. This guide will help you migrate your codebase to the new version, one component at a time.
\nAt the time of writing this guide, sveltekit-superforms
still functions the same as it did for v1, so you won't need to make any changes to the Superforms-specific code.
The changes in this section apply to a number of components in Formsnap, and are not specific to any one component.
\nIn v1, the asChild
prop could be used on any component that rendered an HTML element under the hood to opt out of rendering the element to provide your own.
<!-- This could apply to any component that rendered an HTML element -->\n<Label asChild let:labelAttrs>\n\t<label {...labelAttrs}>Name</label>\n</Label>\n
\nIn v2, this prop has been removed completely in favor of the child
snippet, which is available for all components that render an HTML element, and exposes a props
snippet prop that you can spread onto your own element/component.
<!-- This applies to any component that renders an HTML element -->\n<Label>\n\t{#snippet child({ props })}\n\t\t<label {...props}>Name</label>\n\t{/snippet}\n</Label>\n
\nYou can learn more about the child
snippet in the child documentation.
In v1, you could bind to the el
prop of any component that rendered an element to receive a reference to that HTML element.
<script lang=\"ts\">\n\timport { Label } from \"formsnap\";\n\tlet labelEl: HTMLLabelElement;\n</script>\n\n<Label bind:el={labelEl}>Name</Label>\n
\nIn v2, this prop has been replaced by the ref
prop, which is available for all components that render an HTML element, and exposes a $bindable
reference to the underlying HTML element.
<script lang=\"ts\">\n\timport { Label } from \"formsnap\";\n\tlet labelRef = $state<HTMLLabelElement | null>(null);\n</script>\n\n<Label bind:ref={labelRef}>Name</Label>\n
\nIn v1, the various *Field
components provided a number of slot props for your convenience, such as value
, errors
, tainted
, and constraints
.
<Field {form} name=\"name\" let:value let:errors let:tainted let:constraints>\n\t<!-- ... you can access those slot props here -->\n</Field>\n
\nIn v2, the *Field*
components now provide those values via snippets props to the children
snippet that you can use when needing to access those values.
<Field {form} name=\"name\">\n\t{#snippet children({ value, errors, tainted, constraints })}\n\t\t<!-- ... you can access those snippet props here -->\n\t{/snippet}\n</Field>\n\n<!-- or if you don't need access to those values -->\n\n<Field {form} name=\"name\">\n\t<!-- ... your form components here -->\n</Field>\n
\nThe Control
component in v1 simply expose an attrs
slot prop that was spread onto the control element, like so:
<Control let:attrs>\n\t<input type=\"text\" {...attrs} bind:value={$formData.name} />\n</Control>\n
\nIn v2, the Control
component now provides those attributes via a children
snippet prop, like so:
<Control>\n\t{#snippet children({ props })}\n\t\t<input type=\"text\" {...props} bind:value={$formData.name} />\n\t{/snippet}\n</Control>\n
\nThis change comes with Svelte's deprecation of <slot />
and slot props in favor of Snippets.
Formsnap v2 introduces significant changes to support Svelte 5, requiring updates to your existing codebase. This guide covers all breaking changes and provides clear migration paths for each component.
\nsveltekit-superforms
integration remains unchanged from v1, requiring no modifications to Superforms-specific code.
Formsnap v2 adopts Svelte 5's new snippet pattern, replacing slots, slot props and other traditional patterns. This change affects multiple components throughout the library.
\nThe asChild
prop has been replaced with the more flexible child
snippet pattern.
In v1, the asChild
prop could be used on any component that rendered an HTML element under the hood to opt out of rendering the element to provide your own.
<!-- This could apply to any component that rendered an HTML element -->\n<Label asChild let:labelAttrs>\n\t<label {...labelAttrs}>Name</label>\n</Label>\n
\nIn v2, this prop has been removed completely in favor of the child
snippet, which is available for all components that render an HTML element, and exposes a props
snippet prop that you can spread onto your own element/component.
<!-- This applies to any component that renders an HTML element -->\n<Label>\n\t{#snippet child({ props })}\n\t\t<label {...props}>Name</label>\n\t{/snippet}\n</Label>\n
\nYou can learn more about the child
snippet in the child documentation.
el
-> ref
Element references now use the $bindable
ref
prop.
In v1, you could bind to the el
prop of any component that rendered an element to receive a reference to that HTML element.
<script lang=\"ts\">\n\timport { Label } from \"formsnap\";\n\tlet labelEl: HTMLLabelElement;\n</script>\n\n<Label bind:el={labelEl}>Name</Label>\n
\nIn v2, this prop has been replaced by the ref
prop, which is available for all components that render an HTML element, and exposes a $bindable
reference to the underlying HTML element.
<script lang=\"ts\">\n\timport { Label } from \"formsnap\";\n\tlet labelRef = $state<HTMLLabelElement | null>(null);\n</script>\n\n<Label bind:ref={labelRef}>Name</Label>\n
\nThe various *Field*
components now use snippet props instead of slot props to provide access to their inner state.
In v1, the various *Field
components provided a number of slot props for your convenience, such as value
, errors
, tainted
, and constraints
.
<Field {form} name=\"name\" let:value let:errors let:tainted let:constraints>\n\t<!-- ... you can access those slot props here -->\n</Field>\n
\nIn v2, the *Field*
components now provide those values via snippets props to the children
snippet that you can use when needing to access those values.
<Field {form} name=\"name\">\n\t{#snippet children({ value, errors, tainted, constraints })}\n\t\t<!-- ... you can access those snippet props here -->\n\t{/snippet}\n</Field>\n\n<!-- or if you don't need access to those values -->\n\n<Field {form} name=\"name\">\n\t<!-- ... your form components here -->\n</Field>\n
\nThe Control
component now uses snippet props for attribute passing.
The Control
component in v1 simply expose an attrs
slot prop that was spread onto the control element, like so:
<Control let:attrs>\n\t<input type=\"text\" {...attrs} bind:value={$formData.name} />\n</Control>\n
\nIn v2, the Control
component now provides those attributes via a children
snippet prop, like so:
<Control>\n\t{#snippet children({ props })}\n\t\t<input type=\"text\" {...props} bind:value={$formData.name} />\n\t{/snippet}\n</Control>\n
\nThis change comes with Svelte's deprecation of <slot />
and slot props in favor of Snippets.
getFormField
is now deprecated in favor of useFormField
with a new reactive getter pattern.
In v1, the getFormField
function was used to get a reference to a form field's state for more advance composition patterns.
<script lang=\"ts\">\n\timport { getFormField } from \"formsnap\";\n\n\tconst { form, errors, tainted, constraints } = getFormField();\n</script>\n\nErrors for this field: {$errors}\nConstraints for this field: {$constraints}\nIs this field tainted? {$tainted}\n
\nIn v2, the getFormField
function is marked as deprecated and is aliased to the new useFormField
.
useFormField
returns getters for the various reactive states, rather than an object of stores. This means you should not destructure the object returned by useFormField
and instead use the getters directly.
<script lang=\"ts\">\n\timport { useFormField } from \"formsnap\";\n\n\tconst field = useFormField();\n</script>\n\nErrors for this field: {field.errors}\nConstraints for this field: {field.constraints}\nIs this field tainted? {field.tainted}\n
\nSee the useFormField documentation for more information.
\ngetFormControl
is deprecated in favor of useFormControl
with the new reactive getter pattern.
In v1, the getFormControl
function was used to hook into the state of the closest parent Control
component. This function is useful for building custom components that may encompass both the control and the label.
<script lang=\"ts\">\n\timport { getFormControl } from \"formsnap\";\n\timport { MyLabel, MyInput } from \"$lib/components\";\n\n\texport let label: string;\n\n\tconst { labelAttrs, attrs } = getFormControl();\n</script>\n\n<MyLabel {...$labelAttrs}>\n\t{label}\n</MyLabel>\n\n<MyInput {...$attrs} />\n
\nIn v2, the getFormControl
function is marked as deprecated and is aliased to the new useFormControl
.
useFormControl
returns getters for the various reactive states, rather than an object of stores. This means you should not destructure the object returned by useFormControl
and instead use the getters directly.
<script lang=\"ts\">\n\timport { useFormControl } from \"formsnap\";\n\timport { MyLabel, MyInput } from \"$lib/components\";\n\n\tlet { label }: { label: string } = $props();\n\n\tconst control = useFormControl();\n</script>\n\n<MyLabel {...control.labelProps}>\n\t{label}\n</MyLabel>\n\n<MyInput {...control.props} />\n
\nasChild
instances to use the child
snippetel
bindings with ref
children
snippet props instead of slot propsgetFormField
with useFormField
getFormControl
with useFormControl