--- title: Formisch description: Build forms in React using Formisch and Valibot. links: doc: https://formisch.dev --- import { InfoIcon } from "lucide-react" This guide covers building forms with [Formisch](https://formisch.dev), the lightweight, schema-first, and fully type-safe form library for React. We'll create forms with the `` component, validate them with Valibot schemas, handle errors, and ensure accessibility. ## Demo We'll build the following form. It has a simple text input and a textarea. On submit, we'll validate the form data and display any errors. }> **Note:** For the purpose of this demo, we have intentionally disabled browser validation to show how schema validation and form errors work in Formisch. It is recommended to add basic browser validation in your production code. ## Approach This form leverages Formisch for headless, schema-first form handling. We'll build our form using the `` component, which gives you **complete flexibility over the markup and styling**. - Uses Formisch's `useForm` hook for form state management. - `
` component to wrap the native `` element with submit handling. - `` render-prop component for controlled inputs. - Schema validation using [Valibot](https://valibot.dev). - Type-safe field paths inferred from the schema. ## Form Methods Formisch exposes form operations as **top-level functions** rather than methods on a form object. Import only what you need: ```ts import { getInput, insert, reset, submit } from "@formisch/react" ``` Every method follows the same signature: the **first parameter is always the form store**, and the **second parameter (if necessary) is always a config object**. ```ts // Read a field value const email = getInput(form, { path: ["email"] }) // Reset the form with new initial values reset(form, { initialInput: { email: "", password: "" } }) // Move an item in a field array move(form, { path: ["items"], from: 0, to: 3 }) ``` This design keeps the API flexible and consistent across all methods. You'll see the same `(form, config)` shape used throughout this guide for reading state (`getInput`, `getErrors`), writing state (`setInput`, `setErrors`), form control (`submit`, `validate`, `focus`), and array operations (`insert`, `remove`, `move`, `swap`, `replace`). See the [full methods reference](https://formisch.dev/react/guides/form-methods) for details. ## Anatomy Here's a basic example of a form using the `` component from Formisch and the shadcn `` component. ```tsx showLineNumbers {3-21} {(field) => ( Bug Title Provide a concise title for your bug report. {field.errors && ( ({ message }))} /> )} )} ``` }> **Note:** Formisch ships its own `Field` component. To avoid a name clash with the shadcn `Field`, the examples below import the Formisch one as `FormischField` and keep the shadcn `Field` under its original name. In your own code you can alias either side — just be consistent. ## Form ### Create a form schema We'll start by defining the shape of our form using a Valibot schema. Formisch infers all input and output types directly from this schema. ```tsx showLineNumbers title="form.tsx" import * as v from "valibot" const FormSchema = v.object({ title: v.pipe( v.string(), v.minLength(5, "Bug title must be at least 5 characters."), v.maxLength(32, "Bug title must be at most 32 characters.") ), description: v.pipe( v.string(), v.minLength(20, "Description must be at least 20 characters."), v.maxLength(100, "Description must be at most 100 characters.") ), }) ``` ### Set up the form Next, we'll use the `useForm` hook from Formisch to create our form instance. The schema is passed directly to `useForm` — there is no resolver step. ```tsx showLineNumbers title="form.tsx" {1-2,21-25} import { Form, Field as FormischField, useForm } from "@formisch/react" import type { SubmitHandler } from "@formisch/react" import * as v from "valibot" const FormSchema = v.object({ title: v.pipe( v.string(), v.minLength(5, "Bug title must be at least 5 characters."), v.maxLength(32, "Bug title must be at most 32 characters.") ), description: v.pipe( v.string(), v.minLength(20, "Description must be at least 20 characters."), v.maxLength(100, "Description must be at most 100 characters.") ), }) export function BugReportForm() { const form = useForm({ schema: FormSchema, initialInput: { title: "", description: "", }, }) const handleSubmit: SubmitHandler = (output) => { // Do something with the validated form values. console.log(output) } return (
{/* ... */} {/* Build the form here */} {/* ... */}
) } ``` The `
` component wraps a native `` element. It calls `event.preventDefault()`, runs validation, and only invokes `onSubmit` when the data is valid. The `output` you receive is fully typed from the schema. ### Build the form We can now build the form using the `` component from Formisch and the shadcn `` component. ### Done That's it. You now have a fully accessible form with client-side validation. When you submit the form, the `handleSubmit` function will be called with the validated form data. If the form data is invalid, Formisch will populate `field.errors` for each invalid field and the UI will display them. ## Validation ### Client-side Validation Formisch validates your form data using the Valibot schema you pass to `useForm`. There is no resolver — the schema is the single source of truth for both runtime validation and static types. ```tsx showLineNumbers title="form.tsx" {1,3-6,11} import { useForm } from "@formisch/react" const FormSchema = v.object({ title: v.string(), description: v.optional(v.string()), }) export function ExampleForm() { const form = useForm({ schema: FormSchema, initialInput: { title: "", description: "", }, }) } ``` ### Validation Modes Formisch separates the **first** validation from **subsequent** validations. You configure them with the `validate` and `revalidate` options on `useForm`. ```tsx showLineNumbers title="form.tsx" {3-4} const form = useForm({ schema: FormSchema, validate: "blur", revalidate: "input", }) ``` | Option | Value | Description | | ------------ | ----------- | --------------------------------------------------------------- | | `validate` | `"submit"` | Validate on form submission (default). | | `validate` | `"blur"` | Validate when a field loses focus. | | `validate` | `"input"` | Validate on every input change. | | `validate` | `"initial"` | Validate immediately on form creation. | | `revalidate` | `"input"` | Revalidate on every input change after the first run (default). | | `revalidate` | `"blur"` | Revalidate on blur after the first run. | | `revalidate` | `"submit"` | Revalidate only on form submission. | ## Displaying Errors Display errors next to the field using ``. Formisch returns errors as an array of strings, so map them to the shape `` expects. For styling and accessibility: - Add the `data-invalid` prop to the `` component. - Add the `aria-invalid` prop to the form control such as ``, ``, ``, etc. ```tsx showLineNumbers title="form.tsx" {3,10,12-14} {(field) => ( Email {field.errors && ( ({ message }))} /> )} )} ``` ## Working with Different Field Types Formisch exposes two ways to bind a field to an element: - **Native HTML elements** (like `` and `