# Formisch The modular and type-safe form library for any framework. ## Get started (guides) ### Introduction Formisch is a schema-based, headless form library for Qwik. It manages form state and validation. It is type-safe, fast by default and its bundle size is small due to its modular design. Try it out in our playground! #### Highlights - Small bundle size starting at 2.5 kB - Schema-based validation with Valibot - Type safety with autocompletion in editor - It's fast – DOM updates are fine-grained - Minimal, readable and well thought out API - Supports all native HTML form fields #### Example Every form starts with the `useForm$` hook. It initializes your form's store based on the provided Valibot schema and infers its types. Next, wrap your form in the `
` component. It's a thin layer around the native `` element that handles form validation and submission. Then, you can access the state of a field with the `useField$` hook or the `` component to connect your inputs. ```tsx import { Field, Form, useForm$ } from '@formisch/qwik'; import { component$ } from '@qwik.dev/core'; import * as v from 'valibot'; const LoginSchema = v.object({ email: v.pipe(v.string(), v.email()), password: v.pipe(v.string(), v.minLength(8)), }); export default component$(() => { const loginForm = useForm$({ schema: LoginSchema, }); return ( console.log(output)}> (
{field.errors.value &&
{field.errors.value[0]}
}
)} /> (
{field.errors.value &&
{field.errors.value[0]}
}
)} /> ); }); ``` In addition, Formisch offers several functions (we call them "methods") that can be used to read and manipulate the form state. These include `focus`, `getAllErrors`, `getErrors`, `getInput`, `handleSubmit`, `insert`, `move`, `remove`, `replace`, `reset`, `setErrors`, `setInput`, `submit`, `swap` and `validate`. These methods allow you to control the form programmatically. #### Comparison What makes Formisch unique is its framework-agnostic core, which is fully native to the framework you are using. It works by inserting framework-specific reactivity blocks when the core package is built. The result is a small bundle size and native performance for any UI update. This feature, along with a few others, distinguishes Formisch from other form libraries. My vision for Formisch is to create a framework-agnostic platform similar to Vite, but for forms. #### Partners Thanks to our partners who support the development! Join them and contribute to the sustainability of open source software!

Partners of Formisch

#### Feedback Find a bug or have an idea how to improve the library? Please fill out an issue. Together we can make forms even better! #### License This project is available free of charge and licensed under the MIT license. ### Installation Below you will learn how to add Formisch to your project. #### TypeScript If you are using TypeScript, we recommend that you enable strict mode in your `tsconfig.json` so that all types are calculated correctly. > The minimum required TypeScript version is v5.0.2. ```ts { "compilerOptions": { "strict": true, // ... } } ``` #### Install Valibot Formisch uses [Valibot](https://valibot.com/) for schema-based validation. You need to install it first because it is a peer dependency. ```bash npm install valibot # npm yarn add valibot # yarn pnpm add valibot # pnpm bun add valibot # bun deno add npm:valibot # deno ``` #### Install Formisch You can add Formisch to your project with a single command using your favorite package manager. ```bash npm install @formisch/qwik # npm yarn add @formisch/qwik # yarn pnpm add @formisch/qwik # pnpm bun add @formisch/qwik # bun deno add npm:@formisch/qwik # deno ``` Then you can import it into any JavaScript or TypeScript file. ```ts import { … } from '@formisch/qwik'; ``` ### LLMs.txt If you are using AI to generate forms with Formisch, you can use our LLMs.txt files to help the AI better understand the library. #### What is LLMs.txt? An [LLMs.txt](https://llmstxt.org/) file is a plain text file that provides instructions or metadata for large language models (LLMs). It often specifies how the LLMs should process or interact with content. It is similar to a robots.txt file, but is tailored for AI models. #### Available routes We provide several LLMs.txt routes. Use the route that works best with your AI tool. - [`llms.txt`](/llms.txt) contains a table of contents with links to all Markdown files - [`llms-qwik.txt`](/llms-qwik.txt) contains a table of contents with links to Qwik related files - [`llms-qwik-full.txt`](/llms-qwik-full.txt) contains the Markdown content of the entire Qwik docs - [`llms-qwik-guides.txt`](/llms-qwik-guides.txt) contains the Markdown content of the Qwik guides - [`llms-qwik-api.txt`](/llms-qwik-api.txt) contains the Markdown content of the Qwik API reference #### How to use it To help you get started, here are some examples of how the LLMs.txt files can be used with various AI tools. > Please help us by adding more examples of other AI tools. If you use a tool that supports LLMs.txt files, please [open a pull request](https://github.com/fabian-hiller/valibot/pulls) to add it to this page. ##### Cursor You can add a custom documentation as context in Cursor using the `@Docs` feature. Read more about it [here](https://docs.cursor.com/context/@-symbols/@-docs). ## Main concepts (guides) ### Define your form Creating a form in Formisch starts with defining a Valibot schema. The schema serves as the blueprint for your form, outlining the structure, data types, and validation rules for each field. #### Schema definition Formisch is a schema-first form library built on top of [Valibot](https://valibot.dev/). When you create a form with `useForm$`, TypeScript types are automatically inferred from your schema, giving you full autocompletion and type safety throughout your form without needing to write any manual type definitions. ##### Example schema The following schema defines a form with two required string fields. The `email` field must be a valid email format, and the `password` field must be at least 8 characters long. Each validation includes custom error messages that will be displayed when validation fails. > For more complex schema examples, check out the schemas of our playground. ```ts import * as v from 'valibot'; const LoginSchema = v.object({ email: v.pipe( v.string('Please enter your email.'), v.nonEmpty('Please enter your email.'), v.email('The email address is badly formatted.') ), password: v.pipe( v.string('Please enter your password.'), v.nonEmpty('Please enter your password.'), v.minLength(8, 'Your password must have 8 characters or more.') ), }); ``` #### Schema validation Your schema definition should reflect exactly the data you expect when submitting the form. For example, if the value of a field is optional and will only be submitted in certain cases, your schema should reflect this information by using `v.optional(…)`. ```ts import * as v from 'valibot'; const ProfileSchema = v.object({ name: v.pipe(v.string(), v.nonEmpty()), bio: v.optional(v.string()), // <- Optional field }); ``` Formisch validates your form values against the schema before submission, ensuring that your form can only be submitted if it matches your schema definition. #### Next steps Now that you understand how to define your form schema, continue to the create your form guide to learn how to initialize your form with `useForm$`. ### Create your form Formisch consists of hooks, components and methods. To create a form you use the `useForm$` hook. #### Form hook The `useForm$` hook initializes and returns the store of your form. The store contains the state of the form and can be used with other Formisch hooks, components and methods to build your form. ```tsx import { Field, Form, useForm$ } from '@formisch/qwik'; import { component$ } from '@qwik.dev/core'; import * as v from 'valibot'; const LoginSchema = v.object({ email: v.pipe(v.string(), v.email()), password: v.pipe(v.string(), v.minLength(8)), }); export default component$(() => { const loginForm = useForm$({ schema: LoginSchema, }); return
{/* Form fields will go here */}
; }); ``` ##### Configuration options The `useForm$` hook accepts a configuration object with the following options: - `schema`: Your Valibot schema that defines the form - `initialInput`: Initial values for your form fields (optional) - `validate`: When validation first occurs (optional, defaults to `'submit'`) - `revalidate`: When revalidation occurs after initial validation (optional, defaults to `'input'`) ```tsx const loginForm = useForm$({ schema: LoginSchema, initialInput: { email: 'user@example.com', }, validate: 'initial', revalidate: 'input', }); ``` ##### Multiple forms When a page contains multiple forms, you can create separate form stores for each one: ```tsx import { Form, useForm$ } from '@formisch/qwik'; import { component$ } from '@qwik.dev/core'; import * as v from 'valibot'; const LoginSchema = v.object({ email: v.pipe(v.string(), v.email()), password: v.pipe(v.string(), v.minLength(8)), }); const RegisterSchema = v.object({ username: v.pipe(v.string(), v.minLength(3)), email: v.pipe(v.string(), v.email()), password: v.pipe(v.string(), v.minLength(8)), }); export default component$(() => { const loginForm = useForm$({ schema: LoginSchema }); const registerForm = useForm$({ schema: RegisterSchema }); return ( <>
{/* … */}
{/* … */}
); }); ``` #### Next steps Now that you know how to create a form, continue to the add form fields guide to learn how to connect your input elements to the form using the `Field` component. ### Add form fields To add a field to your form, you can use the `Field` component or the `useField$` hook. Both are headless and provide access to field state for building your form UI. #### Field component The `Field` component has two mandatory properties: `of` which accepts the form store, and `path` which specifies which field to connect. If you use TypeScript, you get full autocompletion for the path based on your schema. ##### Render prop The `Field` component uses a `render$` prop that receives the field store as its parameter. The field store includes signals for the current value, error messages, and props to spread onto your input element. ```tsx import { Field, Form, useForm$ } from '@formisch/qwik'; import { component$ } from '@qwik.dev/core'; import * as v from 'valibot'; const LoginSchema = v.object({ email: v.pipe(v.string(), v.email()), password: v.pipe(v.string(), v.minLength(8)), }); export default component$(() => { const loginForm = useForm$({ schema: LoginSchema, }); return (
( )} /> ( )} /> ); }); ``` > **Important:** If you plan to set initial values with `initialInput` or programmatically control field values using methods like `setInput` or `reset`, you must make your fields controlled by setting the appropriate attributes (like `value`, `checked`, or `selected`). See the controlled fields guide to learn more. ##### Headless design The `Field` component does not render its own UI elements. It is headless and provides only the data layer of the field. This allows you to freely define your user interface. You can use HTML elements, custom components or an external UI library. ##### Path array The `path` property accepts an array of strings and numbers that represents the path to the field in your schema. For top-level fields, it's simply the field name wrapped in an array: ```tsx ( )} /> ``` For nested fields, the path reflects the structure of your schema: ```tsx // For a schema like: v.object({ user: v.object({ email: v.string() }) }) ( )} /> ``` ##### Type safety The API design of the `Field` component results in a fully type-safe form. For example, if you change your schema, TypeScript will immediately alert you if the path is invalid. The field state is also fully typed based on your schema, giving you autocompletion for properties like `field.input`. #### useField$ hook For very complex forms where you create individual components for each form field, Formisch provides the `useField$` hook. It allows you to access the field state directly within your component logic. ```tsx import { useField$ } from '@formisch/qwik'; import type { FormStore } from '@formisch/qwik'; import { component$ } from '@qwik.dev/core'; import * as v from 'valibot'; type EmailInputProps = { form: FormStore>; }; export const EmailInput = component$((props) => { const field = useField$(props.form, { path: ['email'] }); return (
{field.errors.value &&
{field.errors.value[0]}
}
); }); ``` ##### When to use which - **Use `Field` component**: When defining multiple fields in the same component. It ensures you don't accidentally access the wrong field store. - **Use `useField$` hook**: When creating field components for single fields. It allows you to access field state in your component logic. The `Field` component is essentially a thin wrapper around `useField$` that allows you to access the field state within JSX code. #### Field store The field store provides access to the following properties as signals: - `props`: JSX props to spread onto your input element (includes event handlers, ref callback, name attribute, and `autofocus` to automatically focus fields with errors). - `input`: A signal containing the current input value of the field (access with `.value`). - `errors`: A signal containing an array of error messages if validation fails (access with `.value`). - `isTouched`: A signal indicating whether the field has been touched (access with `.value`). - `isDirty`: A signal indicating whether the current input differs from the initial input (access with `.value`). - `isValid`: A signal indicating whether the field passes all validation rules (access with `.value`). #### Next steps Now that you know how to add fields to your form, continue to the input components guide to learn about creating reusable input components for your forms. ### Input components To make your code more readable, we recommend that you develop your own input components if you are not using a prebuilt UI library. There you can encapsulate logic to display error messages, for example. > If you're already a bit more experienced, you can use the input components we developed for our playground as a starting point. You can find the code in our GitHub repository here. #### Why input components? Currently, your fields might look something like this: ```tsx (
{field.errors.value &&
{field.errors.value[0]}
}
)} /> ``` If CSS and a few more functionalities are added here, the code quickly becomes confusing. In addition, you have to rewrite the same code for almost every form field. Our goal is to develop a `TextInput` component so that the code ends up looking like this: ```tsx ( )} /> ``` #### Create an input component In the first step, you create a new file for the `TextInput` component and, if you use TypeScript, define its properties. ```tsx import { component$, type QwikChangeEvent, type QwikFocusEvent, } from '@qwik.dev/core'; type TextInputProps = { name: string; type: 'text' | 'email' | 'tel' | 'password' | 'url' | 'date'; label?: string; placeholder?: string; input: string | undefined; errors: [string, ...string[]] | null; required?: boolean; ref: (element: HTMLInputElement) => void; onFocus$: (event: QwikFocusEvent) => void; onInput$: (event: Event, element: HTMLInputElement) => void; onChange$: (event: QwikChangeEvent) => void; onBlur$: (event: QwikFocusEvent) => void; }; ``` ##### Component function In the next step, add the component function to the file. We can destructure the props directly. ```tsx import { component$, type QwikChangeEvent, type QwikFocusEvent, } from '@qwik.dev/core'; type TextInputProps = { /* ... */ }; export const TextInput = component$( ({ input, label, errors, ...inputProps }) => { // Component JSX will go here } ); ``` ##### JSX code After that, you can add the JSX code to the return statement. ```tsx import { component$, type QwikChangeEvent, type QwikFocusEvent, } from '@qwik.dev/core'; type TextInputProps = { /* ... */ }; export const TextInput = component$( ({ input, label, errors, name, required, ...inputProps }) => { return (
{label && ( )} {errors &&
{errors[0]}
}
); } ); ``` ##### Next steps You can now build on this code and add CSS, for example. You can also follow the procedure to create other components such as `Checkbox`, `Slider`, `Select` and `FileInput`. ##### Final code Below is an overview of the entire code of the `TextInput` component. ```tsx import { component$, type QwikChangeEvent, type QwikFocusEvent, } from '@qwik.dev/core'; type TextInputProps = { name: string; type: 'text' | 'email' | 'tel' | 'password' | 'url' | 'date'; label?: string; placeholder?: string; input: string | undefined; errors: [string, ...string[]] | null; required?: boolean; ref: (element: HTMLInputElement) => void; onFocus$: (event: QwikFocusEvent) => void; onInput$: (event: Event, element: HTMLInputElement) => void; onChange$: (event: QwikChangeEvent) => void; onBlur$: (event: QwikFocusEvent) => void; }; export const TextInput = component$( ({ input, label, errors, name, required, ...inputProps }) => { return (
{label && ( )} {errors &&
{errors[0]}
}
); } ); ``` #### Next steps Now that you know how to create reusable input components, continue to the handle submission guide to learn how to process form data when the user submits the form. ### Handle submission Now your first form is almost ready. There is only one little thing missing and that is the data processing when the form is submitted. #### Submit event To process the values on submission, you need to pass a function to the `onSubmit$` property of the `Form` component. The first parameter passed to the function contains the validated form values. ```tsx import { Field, Form, type SubmitHandler, useForm$ } from '@formisch/qwik'; import { component$ } from '@qwik.dev/core'; import * as v from 'valibot'; const LoginSchema = v.object({ email: v.pipe(v.string(), v.email()), password: v.pipe(v.string(), v.minLength(8)), }); export default component$(() => { const loginForm = useForm$({ schema: LoginSchema, }); const handleSubmit: SubmitHandler = (values) => { // Process the validated form values console.log(values); // { email: string, password: string } }; return (
{/* Form fields will go here */}
); }); ``` The `SubmitHandler` type ensures type safety for your submission handler, automatically inferring the types of validated values from your schema. ##### Prevent default When the form is submitted, `event.preventDefault()` is executed by default to prevent the window from reloading so that the values can be processed directly in the browser and the state of the form is preserved. ##### Loading state While the form is being submitted, you can use `loginForm.isSubmitting.value` to display a loading animation and disable the submit button: ```tsx ``` The form store also provides other reactive properties like `isSubmitted`, `isValidating`, `isTouched`, `isDirty`, `isValid`, and `errors` for tracking form state. Note that `errors` only contains validation errors at the root level of the form — to get all errors from all fields, use the `getAllErrors` method. ##### Async submission The submit handler can be asynchronous, allowing you to perform API calls or other async operations: ```tsx const handleSubmit: SubmitHandler = async (values) => { try { const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(values), }); if (response.ok) { // Handle successful login console.log('Login successful!'); } else { // Handle error console.error('Login failed'); } } catch (error) { console.error('Error during submission:', error); } }; ``` #### Next steps Congratulations! You've learned the core concepts of building forms with Formisch. To learn more about advanced features, check out the form methods guide to discover how to programmatically control your forms. ### Form methods To retrieve the values of your form or to make changes to the form, Formisch provides you with several methods. These apply either to the entire form or to individual fields. #### Reading values To retrieve values from your form, you can use: - `getInput`: Get the current value of a specific field - `getErrors`: Get error messages for a specific field - `getAllErrors`: Get all error messages across the entire form Formisch uses Qwik's signals internally which means that reading values with these methods is reactive. When the form state changes, any component or computation that uses these methods will automatically update to reflect the new state. #### Setting values To manually update form values or errors, use: - `setInput`: Manually update the value of a specific field - `setErrors`: Manually set error messages for a specific field #### Form control To control the form programmatically, use: - `handleSubmit`: Create a submit event handler that validates the form and calls your handler on success - `reset`: Reset the form to its initial state or update initial values - `validate`: Manually trigger validation for the entire form or specific fields - `submit`: Programmatically trigger form submission - `focus`: Focus on a specific field #### Array operations For working with field arrays, Formisch provides: - `insert`: Insert a new item into a field array - `remove`: Remove an item from a field array - `move`: Move an item to a different position in a field array - `swap`: Swap two items in a field array - `replace`: Replace an item in a field array #### API design All methods in Formisch follow a consistent API pattern: the first parameter is always the form store, and the second parameter (if necessary) is always a config object. This design makes the API flexible and consistent across all methods. Once you understand this pattern, you basically understand the entire API design. Here are some examples: ```tsx // Get the value of a field const emailInput = getInput(loginForm, { path: ['email'] }); // Reset the form with new initial values reset(loginForm, { initialInput: { email: '', password: '' } }); // Move an item in a field array move(loginForm, { path: ['items'], from: 0, to: 3 }); ``` #### API reference You can find detailed documentation for each method in our API reference. ## Advanced guides (guides) ### Special inputs As listed in our features, the library supports all native HTML form fields. This includes the HTML `` element. > In our playground you can take a look at such fields and test them out. #### Checkbox A simple checkbox represents a boolean and is `true` when checked or `false` otherwise. ```tsx ( )} /> ``` However, you can also use multiple checkboxes to represent an array of strings. For this you simply have to add the `value` attribute to the HTML `` element. ```tsx { [ { label: 'Bananas', value: 'bananas' }, { label: 'Apples', value: 'apples' }, { label: 'Grapes', value: 'grapes' }, ].map(({ label, value }) => ( ( )} /> )); } ``` #### Select An HTML ` {[ { label: 'Preact', value: 'preact' }, { label: 'Solid', value: 'solid' }, { label: 'Qwik', value: 'qwik' }, ].map(({ label, value }) => ( ))} )} /> ``` However, if you set the `multiple` attribute, multiple options can be selected making the field represent an array of strings. ```tsx ( {/* Set "multiple" to "true" */} )} /> ``` #### File For the HTML `` element it works similar to the HTML `} /> ``` With the `multiple` attribute, users can select multiple files: ```tsx } /> ``` ### Controlled fields By default, all form fields are uncontrolled because that's the default behavior of the browser. For a simple login or contact form this is quite sufficient. #### Why controlled? As soon as your forms become more complex, for example you set initial values or change the values of a form field via `setInput`, it becomes necessary that you control your fields yourself. For example, depending on which HTML form field you use, you may need to set the `value`, `checked` or `selected` attributes. ##### Text input example For a text input field you simply add the `value` attribute and pass the value of the field or in case of `undefined` an empty string: ```tsx ( )} /> ``` ##### Exception for files The HTML `` element is an exception because it cannot be controlled. However, you have the possibility to control the UI around it. For inspiration you can use the code of our `FileInput` component from our playground. #### Numbers and dates To make the fields of numbers and dates controlled, further steps are required, because the `` element natively understands only strings as value. ##### Number input example Since not every input into an `` field is a valid number, for example when typing floating numbers, the value may be `NaN` in between. You have to catch this case, otherwise the whole input will be removed when `NaN` is passed. It is best to encapsulate this logic in a separate component as described in the input components guide. ```tsx import { component$, type QwikChangeEvent, type QwikFocusEvent, useComputed$, } from '@qwik.dev/core'; type NumberInputProps = { name: string; type: 'number'; label?: string; placeholder?: string; input: number | undefined; errors: [string, ...string[]] | null; required?: boolean; ref: (element: HTMLInputElement) => void; onFocus$: (event: QwikFocusEvent) => void; onInput$: (event: Event, element: HTMLInputElement) => void; onChange$: (event: QwikChangeEvent) => void; onBlur$: (event: QwikFocusEvent) => void; }; export const NumberInput = component$( ({ input, label, errors, name, ...inputProps }) => { // Update computed value if not NaN const value = useComputed$(() => !Number.isNaN(input) ? input : undefined ); return (
{label && } {errors &&
{errors[0]}
}
); } ); ``` ##### Date input example A date or a number representing a date must be converted to a string before it can be passed to an `` field. Since it is a calculated value, you can use `useComputed$` for this. ```tsx import { component$, type QwikChangeEvent, type QwikFocusEvent, useComputed$, } from '@qwik.dev/core'; type DateInputProps = { name: string; type: 'date'; label?: string; placeholder?: string; input: Date | number | undefined; errors: [string, ...string[]] | null; required?: boolean; ref: (element: HTMLInputElement) => void; onFocus$: (event: QwikFocusEvent) => void; onInput$: (event: Event, element: HTMLInputElement) => void; onChange$: (event: QwikChangeEvent) => void; onBlur$: (event: QwikFocusEvent) => void; }; export const DateInput = component$( ({ input, label, errors, name, ...inputProps }) => { // Transform date or number to string const value = useComputed$(() => input && !Number.isNaN(typeof input === 'number' ? input : input.getTime()) ? new Date(input).toISOString().split('T', 1)[0] : '' ); return (
{label && } {errors &&
{errors[0]}
}
); } ); ``` #### Next steps Now that you understand controlled fields, you can explore more advanced topics like nested fields and field arrays to handle complex form structures. ### Nested fields To add a little more structure to a complex form or to match the values of the form to your database, you can also nest your form fields in objects as deep as you like. #### Schema definition For example, in the schema below, the first and last name are grouped under the object with the key `name`. ```tsx import * as v from 'valibot'; const ContactSchema = v.object({ name: v.object({ first: v.pipe(v.string(), v.nonEmpty()), last: v.pipe(v.string(), v.nonEmpty()), }), email: v.pipe(v.string(), v.email()), message: v.pipe(v.string(), v.nonEmpty()), }); ``` #### Path array When creating a nested field, use an array with multiple elements for the `path` property to refer to the nested field. The array represents the path through the nested structure. ```tsx ( )} /> ``` If you're using TypeScript, your editor will provide autocompletion for the path based on your schema structure. #### Deep nesting You can nest objects as deeply as needed. Simply extend the path array with additional keys: ```tsx const ProfileSchema = v.object({ user: v.object({ address: v.object({ street: v.string(), city: v.string(), country: v.string(), }), }), }); // Access deeply nested field ( )} />; ``` #### Type safety The path array is fully type-safe. TypeScript will validate that each element in your path corresponds to a valid key in your schema structure, providing autocompletion and compile-time errors if you reference a non-existent path. ### Field arrays A somewhat more special case are dynamically generated form fields based on an array. Since adding, removing, swapping and moving items can be a big challenge here, the library provides the `FieldArray` component which, in combination with various methods, makes it very easy for you to create such forms. > In our playground you can take a look at a form with a field array and test them out. #### Create a field array ##### Schema definition In the following example we create a field array for a todo form with the following schema: ```tsx import * as v from 'valibot'; const TodoFormSchema = v.object({ heading: v.pipe(v.string(), v.nonEmpty()), todos: v.pipe( v.array( v.object({ label: v.pipe(v.string(), v.nonEmpty()), deadline: v.pipe(v.string(), v.nonEmpty()), }) ), v.nonEmpty(), v.maxLength(10) ), }); ``` ##### FieldArray component To dynamically generate the form fields for the todos, you use the `FieldArray` component in combination with `render$` prop. The field array provides an `items` signal that contains unique string identifiers which you can map over to dynamically render items. ```tsx import { Field, FieldArray, Form, useForm$ } from '@formisch/qwik'; import { component$ } from '@qwik.dev/core'; export default component$(() => { const todoForm = useForm$({ schema: TodoFormSchema, initialInput: { heading: '', todos: [{ label: '', deadline: '' }], }, }); return (
console.log(output)}> ( <> {field.errors.value &&
{field.errors.value[0]}
} )} /> (
{fieldArray.items.value.map((item, index) => (
( <> {field.errors.value &&
{field.errors.value[0]}
} )} /> ( <> {field.errors.value &&
{field.errors.value[0]}
} )} />
))} {fieldArray.errors.value &&
{fieldArray.errors.value[0]}
}
)} /> ); }); ``` ##### Path array with index As with nested fields, you use an array for the `path` property. The key difference is that you include the index from the map function to specify which array item you're referencing. This ensures the paths update correctly when items are added, moved, or removed. ```tsx ( )} /> ``` #### Use array methods Now you can use the `insert`, `move`, `remove`, `replace`, and `swap` methods to make changes to the field array. These methods automatically take care of rearranging all the fields. ##### Insert method Add a new item to the array: ```tsx import { insert } from '@formisch/qwik'; ; ``` The `at` option can be used to specify the index where the item should be inserted. If not provided, the item is added to the end of the array. ##### Remove method Remove an item from the array: ```tsx import { remove } from '@formisch/qwik'; ; ``` ##### Move method Move an item from one position to another: ```tsx import { move } from '@formisch/qwik'; ; ``` ##### Swap method Swap two items in the array: ```tsx import { swap } from '@formisch/qwik'; ; ``` ##### Replace method Replace an item with new data: ```tsx import { replace } from '@formisch/qwik'; ; ``` #### Nested field arrays If you need to nest multiple field arrays, the path array syntax makes it straightforward. Simply extend the path with additional array indices: ```tsx const NestedSchema = v.object({ categories: v.array( v.object({ name: v.string(), items: v.array( v.object({ title: v.string(), }) ), }) ), }); ( <> {categoryArray.items.value.map((categoryItem, categoryIndex) => (
( )} /> ( <> {itemArray.items.value.map((item, itemIndex) => ( ( )} /> ))} )} />
))} )} />; ``` You can nest field arrays as deeply as you like. You will also find a suitable example of this in our playground. #### Field array validation As with fields, you can validate field arrays using Valibot's array validation functions. For example, to limit the length of the array: ```tsx const TodoFormSchema = v.object({ todos: v.pipe( v.array( v.object({ label: v.string(), deadline: v.string(), }) ), v.minLength(1), v.maxLength(10) ), }); ``` The validation errors for the field array itself are available in `fieldArray.errors` and can be displayed alongside the array. ### TypeScript Since the library is written in TypeScript and we put a lot of emphasis on the development experience, you can expect maximum TypeScript support. Types are automatically inferred from your Valibot schemas, providing type safety throughout your forms. #### Type inference Formisch uses Valibot's type inference to automatically derive TypeScript types from your schemas. You don't need to define separate types—they're inferred automatically. ```tsx import { Field, Form, useForm$ } from '@formisch/qwik'; import { component$ } from '@qwik.dev/core'; import * as v from 'valibot'; const LoginSchema = v.object({ email: v.pipe(v.string(), v.email()), password: v.pipe(v.string(), v.minLength(8)), }); export default component$(() => { const loginForm = useForm$({ schema: LoginSchema, }); // TypeScript knows the form structure from the schema // loginForm is of type FormStore return (
{ // output is fully typed based on LoginSchema console.log(output.email); // ✓ Type-safe console.log(output.username); // ✗ TypeScript error }} > {/* Form fields */}
); }); ``` ##### Input and output types Valibot schemas can have different input and output types when using transformations. Formisch provides proper typing for both. ```tsx import { Field, Form, useForm$ } from '@formisch/qwik'; import { component$ } from '@qwik.dev/core'; import * as v from 'valibot'; const ProfileSchema = v.object({ age: v.pipe( v.string(), // Input type: string (from HTML input) v.transform((input) => Number(input)), // Output type: number v.number() ), birthDate: v.pipe( v.string(), // Input type: string (ISO date string) v.transform((input) => new Date(input)), // Output type: Date v.date() ), }); export default component$(() => { const profileForm = useForm$({ schema: ProfileSchema, }); return (
{ // output is: { age: number; birthDate: Date } console.log(output.age); // number console.log(output.birthDate); // Date }} > ( // field.input is a signal containing string )} /> ( // field.input is a signal containing string )} /> ); }); ``` ##### Type-safe paths Field paths are fully type-checked. TypeScript will provide autocompletion and catch invalid paths at compile time. ```tsx const UserSchema = v.object({ profile: v.object({ name: v.object({ first: v.string(), last: v.string(), }), email: v.string(), }), }); const userForm = useForm$({ schema: UserSchema }); // ✓ Valid paths - TypeScript provides autocompletion ...} /> ...} /> // ✗ Invalid paths - TypeScript error ...} /> ...} /> ``` #### Type-safe props To pass your form to another component via props, you can use the `FormStore` type along with your schema type to get full type safety. ```tsx import { Form, type FormStore, useForm$ } from '@formisch/qwik'; import { component$ } from '@qwik.dev/core'; import * as v from 'valibot'; const LoginSchema = v.object({ email: v.pipe(v.string(), v.email()), password: v.pipe(v.string(), v.minLength(8)), }); export default component$(() => { const loginForm = useForm$({ schema: LoginSchema, }); return ; }); type FormContentProps = { of: FormStore; }; export const FormContent = component$((props) => { return (
console.log(output)}> {/* Form fields */}
); }); ``` #### Generic field components You can create generic field components with proper TypeScript typing using the `FormStore` type with Valibot's `GenericSchema`. ```tsx import { type FormStore, useField$ } from '@formisch/qwik'; import { component$ } from '@qwik.dev/core'; import * as v from 'valibot'; type EmailInputProps = { of: FormStore>; }; export const EmailInput = component$((props) => { const field = useField$(props.of, { path: ['email'] }); return (
{field.errors.value &&
{field.errors.value[0]}
}
); }); ``` The `v.GenericSchema<{ email: string }>` type ensures that the form passed to `EmailInput` must have an `email` field of type `string`. TypeScript will catch any type mismatches at compile time. #### Available types Most types you need can be imported from `@formisch/qwik`. You can find all available types in our API reference. - `FormStore` - The form store type - `FieldStore` - The field store type - `FieldArrayStore` - The field array store type - `ValidPath` - Type for valid field paths - `ValidArrayPath` - Type for valid array field paths - `Schema` - Base schema type from Valibot - `SubmitHandler` - Type for submit handler functions ## Hooks (API) ### useField Creates a reactive field store of a specific field within a form store. ```ts const field = useField(form, config); ``` #### Generics - `TSchema` - `TFieldPath` #### Parameters - `form` - `config` ##### Explanation With `useField` you create a reactive field store for a specific field path within your form. The field store provides reactive access to the field's input value, validation errors, touched state, and more. It also includes a `props` object that you can spread onto an HTML input or textarea element to automatically handle user interactions like focus, blur, and input events. #### Returns - `field` #### Related The following APIs can be combined with `useField`. ##### Hooks ##### Components ##### Methods ### useFieldArray Creates a reactive field array store of a specific field array within a form store. ```ts const fieldArray = useFieldArray(form, config); ``` #### Generics - `TSchema` - `TFieldArrayPath` #### Parameters - `form` - `config` ##### Explanation With `useFieldArray` you create a reactive field array store for a specific array field within your form. The field array store provides reactive access to the array items (by their IDs), validation errors, touched state, and more. This is particularly useful when working with dynamic arrays of fields that users can add or remove. #### Returns - `fieldArray` #### Related The following APIs can be combined with `useFieldArray`. ##### Hooks ##### Components ##### Methods ## Components (API) ### Form Form component that manages form submission and applies internal state. Wraps form element and passes submission events to the provided handler. ```tsx
...
``` #### Generics - `TSchema` #### Properties - `of` - `onSubmit$` ##### Explanation The `Form` component wraps a native HTML `
` element and provides form submission handling. It automatically prevents the default form submission behavior and calls the `onSubmit$` handler when the form is submitted and validation succeeds. The component uses the `of` prop to reference the form store and renders child elements within the form using Qwik's ``. It also sets `noValidate` on the form element to disable browser validation, as Formisch handles validation internally. #### Related ##### Hooks ##### Components ##### Methods ### Field Headless form field component that provides reactive properties and state. The field component takes a form store, path to field, and a render function that receives a field store to display field state and handle user interactions. ```tsx ``` #### Generics - `TSchema` - `TFieldPath` #### Properties - `of` - `path` - `render$` ##### Explanation The `Field` component is a headless component that doesn't render any UI itself. Instead, it provides a render prop function via the `render$` prop that receives a `FieldStore` containing all the reactive state and props needed to render a form field. The component automatically handles field registration, validation, and state management through the `of` prop that references the form store. #### Related ##### Hooks ##### Components ### FieldArray Headless field array component that provides reactive properties and state for managing array fields. The field array component takes a form store, path to array field, and a render function that receives a field array store to manage array items and handle array operations. ```tsx ``` #### Generics - `TSchema` - `TFieldArrayPath` #### Properties - `of` - `path` - `render$` ##### Explanation The `FieldArray` component is a headless component that doesn't render any UI itself. Instead, it provides a render prop function via the `render$` prop that receives a `FieldArrayStore` containing all the reactive state and methods needed to manage an array of form fields. The component automatically handles array field registration, validation, and state management through the `of` prop that references the form store. #### Related ##### Hooks ##### Components ##### Methods ## Methods (API) ### focus Focuses the first input element of a field. This is useful for programmatically setting focus to a specific field, such as after validation errors or user interactions. ```ts focus(form, config); ``` #### Generics - `TSchema` - `TFieldPath` #### Parameters - `form` - `config` ##### Explanation The `form` parameter is the form store containing the field to focus. The `config` parameter specifies which field to focus using the `path` property, and can optionally include additional focus options. #### Related ##### Primitives ##### Methods ### getAllErrors Retrieves all error messages from all fields in the form by walking through the entire field store tree. This is useful for displaying a summary of all validation errors across the form. ```ts const errors = getAllErrors(form); ``` #### Parameters - `form` ##### Explanation The `form` parameter is the form store to retrieve errors from. The function walks through the entire field store tree to collect all validation error messages from every field. #### Returns - `result` #### Related ##### Primitives ##### Methods ### getErrors Retrieves error messages from the form. When called without a config, returns form-level errors. When called with a path, returns errors for that specific field. ```ts const errors = getErrors(form); const errors = getErrors(form, config); ``` #### Generics - `TSchema` - `TFieldPath` #### Parameters - `form` - `config` ##### Explanation The `form` parameter is the form store to retrieve errors from. The optional `config` parameter specifies which errors to retrieve - either form-level errors (when omitted) or field-specific errors (when a `path` is provided). #### Returns - `result` #### Related ##### Primitives ##### Methods ### getInput Retrieves the current input value of a specific field or the entire form. Returns a partial object as not all fields may have been set. ```ts const input = getInput(form); const input = getInput(form, config); ``` #### Generics - `TSchema` - `TFieldPath` #### Parameters - `form` - `config` ##### Explanation The `form` parameter is the form store to retrieve input from. The optional `config` parameter specifies which field to get input from - if omitted, returns input from the entire form. #### Returns - `result` #### Related ##### Primitives ##### Methods ### handleSubmit Creates a submit event handler for the form that prevents default browser submission, validates the form input, and calls the provided handler if validation succeeds. This is designed to be used with the form's onsubmit event. ```ts const result = handleSubmit(form, handler); ``` #### Generics - `TSchema` #### Parameters - `form` - `handler` ##### Explanation The `form` parameter is the form store to handle submission for. The `handler` parameter is the submit handler function that gets called with the validated form output if validation succeeds. #### Returns - `result` #### Related ##### Primitives ##### Methods ### insert Inserts a new item into a field array at the specified index. All items at or after the insertion point are shifted up by one index. ```ts insert(form, config); ``` #### Generics - `TSchema` - `TFieldArrayPath` #### Parameters - `form` - `config` ##### Explanation The `form` parameter is the form store containing the field array. The `config` parameter specifies the path to the field array, the index where to insert the new item, and optional initial input values for the new item. #### Related ##### Primitives ##### Methods ### move Moves an item within a field array from one index to another. All items between the old and new positions are shifted accordingly. ```ts move(form, config); ``` #### Generics - `TSchema` - `TFieldArrayPath` #### Parameters - `form` - `config` ##### Explanation The `form` parameter is the form store containing the field array. The `config` parameter specifies the path to the field array and the indices to move the item from and to. #### Related ##### Primitives ##### Methods ### remove Removes an item from a field array at the specified index. All items after the removed index are shifted down by one. ```ts remove(form, config); ``` #### Generics - `TSchema` - `TFieldArrayPath` #### Parameters - `form` - `config` ##### Explanation The `form` parameter is the form store containing the field array. The `config` parameter specifies the path to the field array and the index of the item to remove. #### Related ##### Primitives ##### Methods ### replace Replaces an item in a field array at the specified index with a new item. ```ts replace(form, config); ``` #### Generics - `TSchema` - `TFieldArrayPath` #### Parameters - `form` - `config` ##### Explanation The `form` parameter is the form store containing the field array. The `config` parameter specifies the path to the field array, the index of the item to replace, and the new item values. #### Related ##### Primitives ##### Methods ### reset Resets the form or a specific field to its initial state. Optionally keeps certain states like input values, touched state, or errors. ```ts reset(form); reset(form, config); ``` #### Generics - `TSchema` - `TFieldPath` #### Parameters - `form` - `config` ##### Explanation The `form` parameter is the form store to reset. The optional `config` parameter specifies which field to reset (or the entire form if omitted) and which states to preserve during the reset. #### Related ##### Primitives ##### Methods ### setErrors Sets or clears error messages on the form or a specific field. This is useful for setting custom validation errors that don't come from schema validation. ```ts setErrors(form, config); ``` #### Generics - `TSchema` - `TFieldPath` #### Parameters - `form` - `config` ##### Explanation The `form` parameter is the form store to set errors on. The `config` parameter specifies the path and error messages to set, or can be set to null to clear existing errors. #### Related ##### Primitives ##### Methods ### setInput Sets input values for the form or a specific field programmatically. ```ts setInput(form, config); setInput(form, config); ``` #### Generics - `TSchema` - `TFieldPath` #### Parameters - `form` - `config` ##### Explanation The `form` parameter is the form store to set input on. The `config` parameter specifies the path to the field and the new input values to set. #### Related ##### Primitives ##### Methods ### submit Programmatically requests form submission by calling the native `requestSubmit()` method on the underlying form element. ```ts submit(form); ``` #### Parameters - `form` ##### Explanation The `form` parameter is the form store to submit. The function triggers form submission by calling the native `requestSubmit()` method on the underlying form element. #### Related ##### Primitives ##### Methods ### swap Swaps two items in a field array at the specified indices. ```ts swap(form, config); ``` #### Generics - `TSchema` - `TFieldArrayPath` #### Parameters - `form` - `config` ##### Explanation The `form` parameter is the form store containing the field array. The `config` parameter specifies the path to the field array and the two indices of the items to swap. #### Related ##### Primitives ##### Methods ### validate Validates the entire form input against its schema. Returns a safe parse result indicating success or failure with detailed issues. Optionally focuses the first field with validation errors. ```ts const result = validate(form, config?); ``` #### Generics - `TSchema` #### Parameters - `form` - `config` ##### Explanation The `form` parameter is the form store to validate. The optional `config` parameter allows specifying whether to automatically focus the first field with validation errors after validation. #### Returns - `result` #### Related ##### Primitives ##### Methods ## Types (API) ### DeepPartial A utility type that recursively makes all properties and nested properties optional. > This type is too complex to display. Please refer to the [source code](https://github.com/fabian-hiller/formisch/blob/main/packages/core/src/types/utils.ts). #### Related ##### Primitives ### FieldArrayStore Reactive field array store interface with properties for managing array fields. #### Generics - `TSchema` - `TFieldArrayPath` #### Definition - `FieldArrayStore` - `path` - `items` - `errors` - `isTouched` - `isDirty` - `isValid` ### FieldElement A union type representing form field elements that can be registered with a field store. #### Definition - `FieldElement` #### Related ##### Methods ### FieldElementProps Props to spread onto a field element for integration with Qwik form handling. #### Definition - `FieldElementProps` - `name` - `autofocus` - `ref` - `onFocus$` - `onInput$` - `onChange$` - `onBlur$` ### FieldStore Reactive field store interface with properties and element props for field management. #### Generics - `TSchema` - `TFieldPath` #### Definition - `FieldStore` - `path` - `input` - `errors` - `isTouched` - `isDirty` - `isValid` - `props` ### FocusFieldConfig Configuration interface for focusing a field. Used by the `focus` method to specify which field to focus and any additional focus options. #### Generics - `TSchema` - `TFieldPath` #### Definition - `FocusFieldConfig` - `path` #### Related ##### Methods ### FormConfig Form config interface that defines the configuration options for creating a form store with `useForm$`. #### Generics - `TSchema` #### Definition - `FormConfig` - `schema` - `initialInput` - `validate` - `revalidate` ##### Explanation The `FormConfig` object is passed to `useForm$` with a required `schema` property and optional properties for `initialInput`, `validate`, and `revalidate` to control form behavior and validation timing. ### FormStore Form store interface that provides reactive access to the state of a form created with `useForm$`. #### Generics - `TSchema` #### Definition - `FormStore` - `isSubmitting` - `isSubmitted` - `isValidating` - `isTouched` - `isDirty` - `isValid` - `errors` ##### Explanation The `errors` property only contains validation errors at the root level of the form. These are errors from the root object schema validation itself, not errors from nested fields. To retrieve all validation errors from all fields across the entire form, use the `getAllErrors` method instead. ### GetFieldErrorsConfig Configuration interface for retrieving field-specific errors. Used by the `getErrors` method when a specific field path is provided. #### Generics - `TSchema` - `TFieldPath` #### Definition - `GetFieldErrorsConfig` - `path` #### Related ##### Methods ### GetFieldInputConfig Configuration interface for retrieving field-specific input. Used by the `getInput` method when a specific field path is provided. #### Generics - `TSchema` - `TFieldPath` #### Definition - `GetFieldInputConfig` - `path` #### Related ##### Methods ### GetFormErrorsConfig Configuration interface for retrieving form-level errors. Used by the `getErrors` method when no specific field path is provided. #### Definition - `GetFormErrorsConfig` - `path` #### Related ##### Methods ### GetFormInputConfig Configuration interface for retrieving form-level input. Used by the `getInput` method when no specific field path is provided. #### Definition - `GetFormInputConfig` - `path` #### Related ##### Methods ### InsertConfig Configuration interface for inserting items into array fields. Used by the `insert` method. #### Generics - `TSchema` - `TFieldArrayPath` #### Definition - `InsertConfig` - `path` - `at` - `initialInput` #### Related ##### Methods ### PartialValues Utility type that recursively makes all value properties optional, including nested objects and arrays. #### Generics - `TValue` #### Definition - `PartialValues` #### Related ##### Methods ### Path Type definition for readonly arrays of path keys used to access nested properties. #### Definition - `Path` ### PathKey Type definition for keys used in paths to access nested properties in objects and arrays. #### Definition - `PathKey` ### MoveConfig Configuration interface for moving items within array fields. Used by the `move` method. #### Generics - `TSchema` - `TFieldArrayPath` #### Definition - `MoveConfig` - `path` - `from` - `to` #### Related ##### Methods ### RemoveConfig Configuration interface for removing items from array fields. Used by the `remove` method. #### Generics - `TSchema` - `TFieldArrayPath` #### Definition - `RemoveConfig` - `path` - `at` #### Related ##### Methods ### ReplaceConfig Configuration interface for replacing items in array fields. Used by the `replace` method. #### Generics - `TSchema` - `TFieldArrayPath` #### Definition - `ReplaceConfig` - `path` - `at` - `initialInput` #### Related ##### Methods ### RequiredPath A type that represents a required path into an object structure with at least one key. > This type is too complex to display. Please refer to the [source code](https://github.com/fabian-hiller/formisch/blob/main/packages/core/src/types/path.ts). #### Related ##### Methods ### ResetFieldConfig Configuration interface for resetting field-specific input and errors. Used by the `reset` method when a specific field path is provided. #### Generics - `TSchema` - `TFieldPath` #### Definition - `ResetFieldConfig` - `path` - `initialInput` - `keepErrors` - `keepInput` - `keepTouched` #### Related ##### Methods ### ResetFormConfig Configuration interface for resetting form input and errors. Used by the `reset` method when no specific field path is provided. #### Generics - `TSchema` #### Definition - `ResetFormConfig` - `initialInput` - `keepErrors` - `keepInput` - `keepSubmitted` - `keepTouched` #### Related ##### Methods ### Schema Schema type definition that represents any Valibot schema that can be used with Formisch. #### Definition - `Schema` #### Related ##### Primitives ##### Methods ### SetFieldErrorsConfig Configuration interface for setting field-specific errors. Used by the `setErrors` method when a specific field path is provided. #### Generics - `TSchema` - `TFieldPath` #### Definition - `SetFieldErrorsConfig` - `path` - `errors` #### Related ##### Methods ### SetFieldInputConfig Configuration interface for setting field-specific input. Used by the `setInput` method when a specific field path is provided. #### Generics - `TSchema` - `TFieldPath` #### Definition - `SetFieldInputConfig` - `path` - `input` #### Related ##### Methods ### SetFormErrorsConfig Configuration interface for setting form-level errors. Used by the `setErrors` method when no specific field path is provided. #### Definition - `SetFormErrorsConfig` - `path` - `errors` #### Related ##### Methods ### SetFormInputConfig Configuration interface for setting form-level input. Used by the `setInput` method when no specific field path is provided. #### Generics - `TSchema` #### Definition - `SetFormInputConfig` - `path` - `input` #### Related ##### Methods ### SubmitEvent Extended submit event interface for form submission. Extends the native `Event` interface with additional form-specific properties. #### Extends - `Event` #### Definition - `SubmitEvent` - `submitter` #### Related ##### Methods ##### Types ### SubmitHandler Function type for handling form submission. Used by the `handleSubmit` method. #### Generics - `TSchema` #### Parameters - `output` - `event` #### Related ##### Methods ##### Types ### SwapConfig Configuration interface for swapping items in array fields. Used by the `swap` method. #### Generics - `TSchema` - `TFieldArrayPath` #### Definition - `SwapConfig` - `path` - `at` - `and` #### Related ##### Methods ### UseFieldArrayConfig Configuration object for creating a field array store with `useFieldArray`. #### Generics - `TSchema` - `TFieldArrayPath` #### Definition - `UseFieldArrayConfig` - `path` ### UseFieldConfig Configuration object for creating a field store with `useField`. #### Generics - `TSchema` - `TFieldPath` #### Definition - `UseFieldConfig` - `path` ### ValidArrayPath Conditional type that returns the path if valid for array operations, otherwise the first possible valid array path based on the given value. #### Generics - `TValue` - `TPath` #### Definition - `ValidArrayPath` ### ValidateFormConfig Configuration interface for form validation. Used by the `validate` method to specify validation behavior. #### Definition - `ValidateFormConfig` - `shouldFocus` #### Related ##### Methods ### ValidationMode Validation mode type that defines when and how form validation is triggered. #### Definition - `ValidationMode` #### Related ##### Primitives ##### Methods