Formsnap Documentation

repository·main·Indexed 21 days ago

https://github.com/svecosystem/formsnap

A library of accessible, high-level Svelte components designed to enhance the developer experience of sveltekit-superforms. Formsnap provides core components such as Field, Control, Label, FieldErrors, Description, Fieldset, and Legend to manage accessibility and error states automatically when building forms in SvelteKit.

Tokens
21.7K
Snippets
67
Records
91
Agent score
72%

What's inside Formsnap

  1. What is Formsnap?

    main
    Formsnap is a library that wraps sveltekit-superforms to simplify form development in SvelteKit. It provides a set of components that reduce boilerplate code and ensure forms are accessible by default, handling complex ARIA attributes (like aria-describedby, aria-invalid, and aria-required) automatically.
  2. Use the Field component to provide form context

    main

    The Field component provides the necessary context for its children to react to changes in the form state. It manages field-specific information such as IDs required for ARIA attributes and accessibility.

    Important: The Field component does not render any HTML elements itself; it strictly provides context to its children.

    Each Field creates its own context, and children only access the context of their immediate parent Field.

    <Field {form} {name}>
      {#snippet children(fieldProps)}
        <!-- Your field UI here using fieldProps -->
      {/snippet}
    </Field>
  3. How Formsnap components work together

    main

    Formsnap uses a hierarchical component structure to manage form state, accessibility, and validation errors.

    • Field: The primary building block. It takes the form instance and a name (typed to your schema keys) to provide context for all nested components.
    • Control: Used to wrap an input and its label. It provides a props snippet that contains necessary attributes (like id, aria-describedby, etc.) which must be spread onto the input element to keep the label, description, and errors in sync.
    • Label: Used inside a Control to automatically associate a text label with the input.
    • Description: Provides additional context for a field. It is automatically synced with the input's aria-describedby attribute for accessibility.
    • FieldErrors: Displays validation error messages. It is also synced with the input's aria-describedby attribute.
    • Fieldset & Legend: Used to group related fields (like radio groups or checkbox groups). A single Fieldset can manage the Description and FieldErrors for the entire group of controls.
    <Field {form} name="email">
      <Control>
        {#snippet children({ props })}
          <Label>Email</Label>
          <input {...props} type="email" bind:value={$formData.email} />
        {/snippet}
      </Control>
      <Description>Use your company email.</Description>
      <FieldErrors />
    </Field>
  4. How Formsnap works with SvelteKit Superforms

    main

    Formsnap is a wrapper for sveltekit-superforms that provides accessible form components. The workflow follows three main steps:

    1. Define a schema: Use a library like Zod to define your form data structure.
    2. Server-side validation: Use superValidate in your SvelteKit load function to return the form object to the client.
    3. Client-side rendering: Use Formsnap components (Field, Control, FieldErrors, etc.) in your Svelte component to build the UI. You connect the Formsnap components to your Superforms instance by passing the form object and the specific field name.
  5. Style Formsnap components using Data Attributes

    main

    Formsnap provides specific data-* attributes on its rendered elements. You can use these attributes as CSS selectors to style components globally or to target specific states (like validation errors) on parent elements. This is useful for styling elements that change based on the form's state without needing to manage complex class logic manually.

    [data-fs-error] {
    	color: red;
    }
    
    [data-fs-control] {
    	border: 1px solid #ccc;
    }
  6. How Formsnap simplifies Superforms usage

    main

    When using sveltekit-superforms alone, you must manually manage accessibility attributes, error spans, and descriptions for every input. Formsnap abstracts this using a component-based approach where a <Field> manages the context for a specific form field, and <Control> provides the necessary accessibility props to the underlying input via a snippet.

    <script lang="ts">
    	import { Field, Control, Label, FieldErrors, Description } from "formsnap";
    	import { signupFormSchema } from "./schema.ts";
    	import { zodClient } from "sveltekit-superforms/adapters";
    	import { superForm } from "sveltekit-superforms";
    	let { data } = $props();
    
    	const form = superForm(data.form, {
    		validators: zodClient(signupFormSchema),
    	});
    	const { form: formData, enhance } = form;
    </script>
    
    <form method="POST" use:enhance>
    	<Field {form} name="name">
    		<Control>
    			{#snippet children({ props })}
    				<Label>Name</Label>
    				<input {...props} bind:value={$formData.name} />
    			{/snippet}
    		</Control>
    		<Description>Be sure to use your real name.</Description>
    		<FieldErrors />
    	</Field>
    </form>
  7. Use the Control component to associate labels with inputs

    main

    In Formsnap, a Control is used to wrap any interactive form element (like an <input>, <select>, or custom checkbox) to ensure it is correctly associated with a Label for accessibility.

    Because the Control component does not render an HTML element itself, it uses a children snippet to provide necessary attributes (like id, aria-describedby, etc.) to the actual input element. This separation allows Field components to be used for multi-control inputs like radio groups or checkbox groups without being tied to a single specific element.

    To use it, wrap your input inside the Control component and use the children snippet to access and spread the provided props onto your element.

    <Control>
    	{#snippet children({ props })}
    		<input type="text" {...props} bind:value={$formData.name} />
    	{/snippet}
    </Control>
  8. Use the Fieldset component to group related controls

    main

    The Fieldset component follows W3C recommendations for grouping related form controls (like radio or checkbox groups) by rendering a <fieldset> element.

    Key requirements:

    • It must always be used with the Legend component to provide a title for the group.
    • It automatically includes the Field component functionality, so you only need to pass the form and name props.

    When to use it:

    1. Radio Groups: To group multiple radio buttons that share a single field value.
    2. Checkbox Groups: To group multiple checkboxes used for multiple selections under one field.
    3. Note on Grouped Form Sections: For large sections that do not represent a single field (e.g.,
    <Fieldset {form} name="theme">
    	<Legend>Select your theme</Legend>
    	{#each themes as theme}
    		<Control>
    			{#snippet children({ props })}
    				<input {...props} type="radio" bind:group={$formData.theme} value={theme} />
    				<Label>{theme}</Label>
    			{/snippet}
    		</Control>
    	{/each}
    	<Description>Help us understand your preferences by selecting a theme.</Description>
    	<FieldErrors />
    </Fieldset>
  9. Create a new Svelte project with sv

    main

    Use the sv CLI to scaffold a new Svelte project. You can either initialize the project in the current directory or specify a new directory name.

    To create a project in the current directory:

    npx sv create

    To create a project in a specific directory (e.g., my-app):

    npx sv create my-app
  10. Style Formsnap components with CSS Frameworks

    main

    If you are using a utility-first CSS framework like TailwindCSS or UnoCSS, you can style Formsnap components by passing the class prop directly to them. This allows you to apply framework-specific utility classes to the underlying HTML elements rendered by the components.

    <script lang="ts">
    	import { Label } from "formsnap";
    </script>
    
    <form>
    	<!-- ... -->
    	<Label class="text-black hover:text-orange-500">First Name</Label>
    	<!-- ... -->
    </form>
  11. Migrate Field slot props to Snippet props

    main

    The *Field* components (e.g., Field) no longer use slot props to expose state. Instead, they provide state values via the children snippet.

    v1 (Old):

    <Field {form} name="name" let:value let:errors let:tainted let:constraints>
    	<!-- ... -->
    </Field>

    v2 (New):

    <Field {form} name="name">
    	{#snippet children({ value, errors, tainted, constraints })}
    		<!-- ... access values here -->
    	{/snippet}
    </Field>

    If you do not need to access the field state, you can simply use the component without a snippet:

    <Field {form} name="name">
    	<!-- ... your form components here -->
    </Field>