Formsnap: accessible Svelte form components wrapped around Superforms
Functional, accessible, and powerful form components for Svelte. 🫰
At a glance
- What is it?
- Formsnap is an Svelte component library that sits on top of sveltekit-superforms and supplies the accessible markup a form needs: a Field that owns the wiring, a Control that hands you the right props, and a FieldErrors that renders validation messages. It is MIT licensed, works with Svelte 5 snippets, and is published from a changesets driven monorepo.
- Who is it for?
- Formsnap suits a SvelteKit team that already uses sveltekit-superforms and is tired of hand-wiring ids, aria attributes and error announcements on every input, and that wants a schema library chosen by them rather than by the form library. It is the wrong choice if you want a standalone form solution, because it is a wrapper and takes Superforms as a hard dependency, and it is the wrong choice for a Svelte 4 codebase, since the components are built on Svelte 5 snippets.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A layer above Superforms, not a replacement for it
Formsnap describes itself as functional, accessible and powerful form components for Svelte, and the project page is clear about what it is. The goal of the library is to make working with the already incredible sveltekit-superforms even more pleasant, by wrapping it with accessible form components.
That sentence sets the scope precisely. Formsnap is not a form engine, it does not do validation, and it does not talk to your server. It is a set of Svelte components that sit on top of Superforms and take over the part of form work that is tedious and easy to get wrong by hand: connecting a label to its input, tying an input to its error message, and giving assistive technology the right relationships.
The consequence for adoption is that you are making two decisions. You choose Superforms as your form handling layer, and you choose Formsnap as your component layer. If your team is happy with Superforms and would rather not hand-write a hundred label and aria combinations, the extra dependency is cheap. If you want to own the markup completely, or you use a different form library, Formsnap is the wrong shape of answer.
The licence is MIT, the documentation site is formsnap.dev, and the repository is organised as a monorepo for Formsnap with a packages directory and a docs directory.
The component set and the snippet pattern
The exported components are Field, Label, FieldErrors, Control, Description, Fieldset and Legend.
Field owns a single form field by name. Control wraps the actual input element and passes the accessibility wiring down through a snippet. Label renders the label text. FieldErrors renders the validation messages for that field. Description renders helper text. Fieldset and Legend group related controls, such as a radio group, with the right semantics for a group.
The pattern that makes this work is the snippet. The Control component hands its child a props object, and the consumer spreads it onto the real input:
<Field {form} name="email">
<Control>
{#snippet children({ props })}
<Label>Email</Label>
<input type="email" {...props} bind:value={$formData.email} />
{/snippet}
</Control>
<Description>We'll provide critical updates about your account via email.</Description>
<FieldErrors />
</Field>The {...props} spread is the whole point. Whatever the control needs, the id that ties it to the label, the aria attributes that reference the description and the error message, the invalid state, comes from the Field and is applied by spreading. The developer writes an ordinary input element and does not think about accessibility wiring.
It also means the component is not tied to a particular input element. The examples show an input, a textarea and a select all receiving the same props, so a custom control works as long as it accepts the same attributes.
Three steps: schema, load function, page
The setup is described as handling the initial Superforms setup just as you normally would, where you define a schema and return the form from your load function. The library adds nothing to that part, which is deliberate.
Step one is the schema. The documented example uses Zod, with an object holding a required email, an optional bio capped at 250 characters, a boolean for marketing emails that defaults to true, and two enums with defaults:
export const load: PageServerLoad = async () => {
return {
form: await superValidate(zod(settingsFormSchema)),
};
};Step two is returning that form from the page's load function, using superValidate with the Zod adapter, which is the standard Superforms call.
Step three is the page, where superForm is constructed with the client validator so that errors are reported as the user types rather than only on submit:
const form = superForm(data.form, {
validators: zodClient(settingsFormSchema),
});
const { form: formData, enhance } = form;The form element then uses use:enhance for progressive enhancement, and each field follows the Field, Control, snippet, Label, input, Description, FieldErrors shape shown above. That is the whole workflow, and it is short enough that a team can read the entire surface in one sitting.
Groups and radio buttons need their own components
One part of the example is worth isolating, because it shows the library handling a case that hand-written markup usually gets wrong.
A radio group is not a set of independent fields, it is one field with several controls, and it needs a group label and grouping semantics. The example uses Fieldset and Legend for that:
<Fieldset {form} name="theme">
<Legend>Select your theme</Legend>
{#each ["light", "dark"] as theme}
<Control>
{#snippet children({ props })}
<input {...props} type="radio" bind:group={$formData.theme} value={theme} />
<Label>{theme}</Label>
{/snippet}
</Control>
{/each}
<FieldErrors />
</Fieldset>The Fieldset takes the form and the field name, so the errors for that field are collected at the group level by a single FieldErrors, and each radio inside still goes through Control so that it receives its own wiring. The Legend labels the group, and the bind:group binding means the value lives in one place rather than in per radio state.
That is a small piece of the library, and it is a good illustration of the general shape. The components are not generating markup you then have to adjust, they are a set of conventions that encode the correct relationships, and the price is that you use them as intended rather than assembling your own.
A monorepo with changesets and a docs site
The repository root is a private package named root with the description Monorepo for Formsnap, and it is where the workspace tooling lives.
The release process is visible in the scripts. ci:publish runs the package build and then changeset publish, and the @changesets/cli plus the GitHub changelog plugin in the dev dependencies show that version bumps and changelog entries are generated rather than written by hand. There is a .changeset directory, which is where pending changes are described before a release.
The build scripts are layered: build runs the recursive package build, build:packages builds only the packages in parallel, build:content builds the docs content in parallel, and check builds the packages and then runs the per package check. The dev script runs svelte-kit sync across the packages and then starts every package's dev server in parallel, which is why the docs site and the library can be developed at the same time.
The toolchain requirements are modest and explicit: pnpm 9.0.0 or newer, Node 18 or newer, and pnpm 9.14.4 pinned as the package manager. Formatting is Prettier with the Svelte and Tailwind plugins, and linting is ESLint with eslint-plugin-svelte and the flat config file at the root. wrangler in the dev dependencies points at a Cloudflare Workers runtime, which is the usual way a documentation site gets deployed.
The author is Hunter Johnston, better known as @huntabyte, and the project credits that person and the community, with sponsor logos for huntabyte. The community channel is a Discord server for questions and collaboration.
Svelte 5 snippets are the requirement to check
The single most important practical detail for someone evaluating this library is that the components are written for Svelte 5.
Every example uses the snippet syntax, with the children snippet declared inside Control and its props argument destructured, and the page component uses the runes style props destructuring from $props. The root package pins svelte at 5.11.0 or newer, and the type script is 5.6.3 or newer.
That is a hard constraint for a team still on Svelte 4, because the snippet mechanism does not exist there and the components would have to be written differently. It also means the library is riding the current Svelte release line, which is a reasonable place to be for a component library with a small surface.
The other constraint is the dependency chain. The installation line is:
npm i formsnap sveltekit-superforms <your-schema-library>The schema library is a parameter rather than a fixed choice, and the examples use Zod with the zodClient validator from Superforms adapters. So the library is schema agnostic in principle and Zod centric in practice, because that is what the documentation demonstrates.
And Formsnap itself is the third layer. You install Superforms because Formsnap wraps it, and you install a schema library because Superforms needs one. The chain is short and each layer is small, but it is a chain, and an application that already has its own form abstraction has to weigh that against the value of not writing accessibility wiring by hand.
A 2.x line with work still landing
The release history shows a project that shipped a major version and then went quiet on tags while continuing to work.
The recent releases are [email protected] on 2024-12-13, a [email protected] pre-release in October 2024, and [email protected] on 2025-04-09. The last push to the repository was on 2026-10-19, which is well after that.
So the code is being worked on and the published version is from eighteen months earlier. For a component library the consequences are milder than for a runtime, since a stale patch release rarely breaks an application, but it does mean the version in your lockfile may not contain fixes you saw in the repository.
The documentation is the more practical gap to check. The project page points to formsnap.dev for more documentation, and the repository carries a docs directory that is built as part of the workspace, so the reference docs live with the code rather than behind a separate site.
The judgement on the library itself is fairly simple. It is a thin, well-scoped layer that takes one specific chore, the accessible wiring of a form control, and does it once for everyone. If your team has already written that wiring in a shared component of its own, this library will not add much. If you have not, and you are using Superforms, it removes a class of bug that is easy to introduce and hard to notice.
Editorial conclusion
Formsnap suits a SvelteKit team that already uses sveltekit-superforms and is tired of hand-wiring ids, aria attributes and error announcements on every input, and that wants a schema library chosen by them rather than by the form library. It is the wrong choice if you want a standalone form solution, because it is a wrapper and takes Superforms as a hard dependency, and it is the wrong choice for a Svelte 4 codebase, since the components are built on Svelte 5 snippets. Verify three things before adopting it: that your Superforms version is one the wrapper supports, that your schema library has a client validator in the adapter you need, and that the components cover the controls you use, since the documented set is Field, Label, FieldErrors, Control, Description, Fieldset and Legend.
Frequently asked questions
What is Formsnap?
It is a set of accessible form components for Svelte that wrap sveltekit-superforms. The project describes it as making the already capable Superforms more pleasant to use, by supplying the Field, Label, FieldErrors, Control, Description, Fieldset and Legend components that carry the accessibility wiring for you.
How do I install Formsnap?
With npm i formsnap sveltekit-superforms and your schema library, for example npm i formsnap sveltekit-superforms zod. The schema library is a parameter in the command because the library itself does not do validation, it wraps Superforms, which does.
How does the Control component work in Formsnap?
Control renders its child through a snippet and passes a props object that the developer spreads onto the real input, textarea or select. The props carry the id, the aria attributes and the invalid state, so the input is wired to its label, its description and its errors without the developer handling them by hand.
Does Formsnap work with Svelte 4?
The components are written for Svelte 5. Every example uses snippet syntax with children and props destructured from $props, and the workspace pins svelte at 5.11.0 or newer, so a Svelte 4 codebase would need a different approach.
How are Formsnap releases made?
The repository is a monorepo whose root scripts drive the release: ci:publish builds the packages and then runs changeset publish, with the @changesets/cli and a GitHub changelog plugin in the dev dependencies. The most recent published version is [email protected] from 2025-04-09, and the last push to the repository was on 2026-10-19.
What licence does Formsnap use?
MIT, published under that licence with the licence file at the root of the repository. The project is made by @huntabyte and the community, is sponsored through GitHub Sponsors, and has a Discord server for questions and collaboration.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/svecosystem-formsnap)Was this useful?