diff --git a/.github/ISSUE_TEMPLATE/3-bug_report.yml b/.github/ISSUE_TEMPLATE/3-bug_report.yml
index 137a488..176aba7 100644
--- a/.github/ISSUE_TEMPLATE/3-bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/3-bug_report.yml
@@ -24,7 +24,7 @@ body:
attributes:
label: Reproduction
description: |
- Please provide a link to a repo or Stackblitz that can reproduce the problem you ran into. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "needs reproduction" label. If no reproduction is provided within a reasonable time-frame, the issue will be closed.
+ Please provide a link to a repo or StackBlitz that can reproduce the problem you ran into. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "needs reproduction" label. If no reproduction is provided within a reasonable time-frame, the issue will be closed.
placeholder: Reproduction
validations:
required: true
diff --git a/.github/workflows/build-preview.yml b/.github/workflows/build-preview.yml
index 262150c..7788079 100644
--- a/.github/workflows/build-preview.yml
+++ b/.github/workflows/build-preview.yml
@@ -11,7 +11,7 @@ on:
jobs:
build-preview:
- runs-on: ubuntu-latest
+ runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
@@ -30,4 +30,4 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: preview-build
- path: sites/docs/.svelte-kit/cloudflare
+ path: docs/.svelte-kit/cloudflare
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2183865..0c2c6bc 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -4,7 +4,18 @@ on:
push:
branches:
- main
+ - next
+ paths-ignore:
+ - ".changeset/**"
+ - README.md
+ - ".vscode/**"
+ - CHANGELOG.md
pull_request:
+ paths-ignore:
+ - ".changeset/**"
+ - README.md
+ - ".vscode/**"
+ - CHANGELOG.md
concurrency:
group: ${{ github.workflow }}-${{ github.event.number || github.sha }}
@@ -13,7 +24,7 @@ concurrency:
jobs:
Check:
name: Run svelte-check
- runs-on: ubuntu-latest
+ runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
@@ -29,7 +40,7 @@ jobs:
run: pnpm check
Lint:
- runs-on: ubuntu-latest
+ runs-on: macos-latest
name: Lint
steps:
- uses: actions/checkout@v4
@@ -42,22 +53,4 @@ jobs:
- name: Install dependencies
run: pnpm install
- - name: Lint
- run: pnpm lint
-
- Test:
- name: Test
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- - uses: pnpm/action-setup@v4
- - uses: actions/setup-node@v4
- with:
- node-version: 20
- cache: pnpm
-
- - name: Install dependencies
- run: pnpm install
-
- - name: Test
- run: pnpm test
+ - run: pnpm lint
diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml
index d61b2cd..9fbf454 100644
--- a/.github/workflows/deploy-preview.yml
+++ b/.github/workflows/deploy-preview.yml
@@ -14,7 +14,7 @@ permissions:
jobs:
deploy-preview:
- runs-on: ubuntu-latest
+ runs-on: macos-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Download build artifact
diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml
index c82b2d6..030ec10 100644
--- a/.github/workflows/deploy-prod.yml
+++ b/.github/workflows/deploy-prod.yml
@@ -4,12 +4,13 @@ on:
branches:
- main
paths:
- - sites/docs/**
+ - docs/**
- packages/formsnap/**
+ workflow_dispatch:
jobs:
deploy-production:
- runs-on: ubuntu-latest
+ runs-on: macos-latest
permissions:
contents: read
deployments: write
@@ -36,5 +37,5 @@ jobs:
githubToken: ${{ secrets.GITHUB_TOKEN }}
projectName: formsnap
directory: ./.svelte-kit/cloudflare
- workingDirectory: sites/docs
+ workingDirectory: docs
deploymentName: Production
diff --git a/.github/workflows/preview-release.yml b/.github/workflows/preview-release.yml
new file mode 100644
index 0000000..617ff99
--- /dev/null
+++ b/.github/workflows/preview-release.yml
@@ -0,0 +1,28 @@
+name: Publish Preview Release
+on:
+ pull_request:
+ types: [ready_for_review, synchronize, opened, labeled]
+ paths: [packages/**]
+
+jobs:
+ preview-release:
+ if: github.repository == 'svecosystem/formsnap' && contains(github.event.pull_request.labels.*.name, 'publish:preview')
+ timeout-minutes: 5
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: pnpm/action-setup@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: pnpm
+
+ - name: install dependencies
+ run: pnpm install
+
+ - name: build
+ run: pnpm build:packages
+
+ - name: publish preview
+ run: |
+ pnpx pkg-pr-new@0.0 publish --pnpm --compact './packages/*'
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 3688810..d864b6f 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -14,14 +14,11 @@ jobs:
contents: write # to create release (changesets/action)
pull-requests: write # to create pull request (changesets/action)
name: Release
- runs-on: ubuntu-latest
+ runs-on: macos-latest
steps:
- - name: Checkout Repo
- uses: actions/checkout@v4
+ - uses: actions/checkout@v4
with:
- # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
fetch-depth: 0
-
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
@@ -35,8 +32,6 @@ jobs:
id: changesets
uses: changesets/action@v1
with:
- commit: "chore(release): version package"
- title: "chore(release): version package"
publish: pnpm ci:publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.prettierignore b/.prettierignore
index f048cbe..6ee1dd1 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -20,4 +20,6 @@ package.json
CHANGELOG.md
vite.config.js.timestamp-*
-vite.config.ts.timestamp-*
\ No newline at end of file
+vite.config.ts.timestamp-*
+
+docs/.velite/**/*
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index ec0efcf..0000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- // Enable the ESlint flat config support
- "eslint.useFlatConfig": true,
-
- // Auto fix
- "editor.codeActionsOnSave": {
- "source.fixAll.eslint": "explicit",
- "source.organizeImports": "never"
- },
-
- // Enable eslint for all supported languages
- "eslint.validate": [
- "javascript",
- "javascriptreact",
- "typescript",
- "typescriptreact",
- "vue",
- "svelte",
- "html",
- "markdown",
- "json",
- "jsonc",
- "yaml",
- "toml",
- "astro"
- ]
-}
diff --git a/sites/docs/.gitignore b/docs/.gitignore
similarity index 64%
rename from sites/docs/.gitignore
rename to docs/.gitignore
index 7fda903..79518f7 100644
--- a/sites/docs/.gitignore
+++ b/docs/.gitignore
@@ -1,14 +1,21 @@
-.DS_Store
node_modules
-/build
+
+# Output
+.output
+.vercel
/.svelte-kit
-/package
+/build
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Env
.env
.env.*
!.env.example
+!.env.test
+
+# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
-.contentlayer
-.contentlayer/
-/.contentlayer
-.vercel
\ No newline at end of file
diff --git a/docs/.npmrc b/docs/.npmrc
new file mode 100644
index 0000000..b6f27f1
--- /dev/null
+++ b/docs/.npmrc
@@ -0,0 +1 @@
+engine-strict=true
diff --git a/docs/.velite/docs.json b/docs/.velite/docs.json
new file mode 100644
index 0000000..aed4a35
--- /dev/null
+++ b/docs/.velite/docs.json
@@ -0,0 +1,594 @@
+[
+ {
+ "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.
\n
The Same Form, Two Ways
\n
To 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
Superforms 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
\n
That'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
All is not lost though, as the whole idea behind Formsnap is to make this process simpler, without sacrificing the flexibility that Superforms provides.
\n
Superforms + 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
\n
That's it! We just condensed a bunch of code, while retaining the same functionality.
\n
Next Steps
\n
To 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": "\n
Installation
\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.
\n
npm install formsnap sveltekit-superforms zod\n
\n
Tutorial: Build a settings form
\n
Before 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\n
Define a Zod schema
\n
This 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
import { 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
Looking 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:
Of course, there are other ways to represent the data, but this is the approach we'll take for this tutorial.
\n
Return the form from a load function
\n
In Superforms fashion, we'll return the form from a load function to seamlessly merge our PageData and ActionData.
\n
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
\n
Setup the form in the page component
\n
Now 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
\n
We'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.
\n
Constructing a form field
\n
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.
\n
We'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
\n
We 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.
\n
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
\n
We've first added the Control component. Controls 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.
\n
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.
\n
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.
\n
And that's really all it takes to setup a form field. Let's continue on with the rest of the fields.
\n
Add 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
\n
You 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\n
And that's it! You've now successfully built a settings form with Formsnap!
\n
Next Steps
\n
Now 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.
\n
Data Attributes
\n
Data 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.
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.
\n
Here's an example of how you might use these data attributes to style the various parts of your form:
If 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:
",
+ "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 `