Formisch
Formisch is a schema-based, headless form library for Qwik. It manages form state and validates your fields with a Valibot schema. The headless design gives you full control over the visual appearance of your form.
Formisch requires Qwik v2, which is currently in beta. If you are using Qwik v1, use Modular Forms, its predecessor by the same author.
To get started, install the @formisch/qwik and valibot packages:
pnpm install @formisch/qwik valibotnpm install @formisch/qwik valibotyarn add @formisch/qwik valibotbun install @formisch/qwik valibotDefine your form
Instead of a separate type definition, in Formisch your Valibot schema is the single source of truth. It defines the structure of your form, validates your fields and infers the TypeScript types of your input and output values.
import * as v from 'valibot';
const LoginSchema = v.object({
email: v.pipe(
v.string(),
v.nonEmpty('Please enter your email.'),
v.email('The email address is badly formatted.'),
),
password: v.pipe(
v.string(),
v.nonEmpty('Please enter your password.'),
v.minLength(8, 'Your password must have 8 characters or more.'),
),
});
Create a form
To create a form, you use the useForm$ hook. It initializes and returns the store of your form, which you pass to the other Formisch components and methods.
import { component$ } from '@qwik.dev/core';
import { Field, Form, useForm$ } from '@formisch/qwik';
export default component$(() => {
const loginForm = useForm$(() => ({
schema: LoginSchema,
}));
return <Form of={loginForm}>…</Form>;
});
Add form fields
With the Field component you connect a field to your form. It is headless and provides you direct access to its current state via the render$ prop. The path to the field is type-safe and autocompleted based on your schema. Spread field.props onto an <input />, <select /> or <textarea /> element to connect it to your form.
<Form of={loginForm}>
<Field
of={loginForm}
path={['email']}
render$={(field) => (
<div>
<input {...field.props} value={field.input.value} type="email" />
{field.errors.value && <div>{field.errors.value[0]}</div>}
</div>
)}
/>
<Field
of={loginForm}
path={['password']}
render$={(field) => (
<div>
<input {...field.props} value={field.input.value} type="password" />
{field.errors.value && <div>{field.errors.value[0]}</div>}
</div>
)}
/>
<button type="submit">Login</button>
</Form>
This API design results in a fully type-safe form. Furthermore, it gives you full control over the user interface. You can develop your own input components or connect a pre-built component library.
Handle submission
To process the values when the form is submitted, pass a function to the onSubmit$ property of the Form component. The first parameter contains the validated values, typed according to the output of your schema.
import { $, component$ } from '@qwik.dev/core';
import { Field, Form, type SubmitHandler, useForm$ } from '@formisch/qwik';
export default component$(() => {
const loginForm = useForm$(() => ({
schema: LoginSchema,
}));
const handleSubmit = $<SubmitHandler<typeof LoginSchema>>((output) => {
// Process the validated values
});
return (
<Form of={loginForm} onSubmit$={handleSubmit}>
…
</Form>
);
});
While the form is being submitted, you can use loginForm.isSubmitting.value to display a loading state and disable the submit button.
Summary
You now know the basic building blocks of Formisch. For more details on nested fields, field arrays and the available form methods, see the documentation. You can also try Formisch in the playground.