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.

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<typeof LoginSchema>

  return (
    <Form
      of={loginForm}
      onSubmit$={(output) => {
        // output is fully typed based on LoginSchema
        console.log(output.email); // ✓ Type-safe
        console.log(output.username); // ✗ TypeScript error
      }}
    >
      {/* Form fields */}
    </Form>
  );
});

Input and output types

Valibot schemas can have different input and output types when using transformations. Formisch provides proper typing for both.

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 (
    <Form
      of={profileForm}
      onSubmit$={(output) => {
        // output is: { age: number; birthDate: Date }
        console.log(output.age); // number
        console.log(output.birthDate); // Date
      }}
    >
      <Field
        of={profileForm}
        path={['age']}
        render$={(field) => (
          // field.input is a signal containing string
          <input {...field.props} value={field.input.value ?? ''} type="text" />
        )}
      />

      <Field
        of={profileForm}
        path={['birthDate']}
        render$={(field) => (
          // field.input is a signal containing string
          <input {...field.props} value={field.input.value ?? ''} type="date" />
        )}
      />

      <button type="submit">Submit</button>
    </Form>
  );
});

Type-safe paths

Field paths are fully type-checked. TypeScript will provide autocompletion and catch invalid paths at compile time.

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
<Field of={userForm} path={['profile', 'name', 'first']} render$={(field) => ...} />
<Field of={userForm} path={['profile', 'email']} render$={(field) => ...} />

// ✗ Invalid paths - TypeScript error
<Field of={userForm} path={['profile', 'age']} render$={(field) => ...} />
<Field of={userForm} path={['username']} render$={(field) => ...} />

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.

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 <FormContent of={loginForm} />;
});

type FormContentProps = {
  of: FormStore<typeof LoginSchema>;
};

export const FormContent = component$<FormContentProps>((props) => {
  return (
    <Form of={props.of} onSubmit$={(output) => console.log(output)}>
      {/* Form fields */}
    </Form>
  );
});

Generic field components

You can create generic field components with proper TypeScript typing using the FormStore type with Valibot's GenericSchema.

import { type FormStore, useField$ } from '@formisch/qwik';
import { component$ } from '@qwik.dev/core';
import * as v from 'valibot';

type EmailInputProps = {
  of: FormStore<v.GenericSchema<{ email: string }>>;
};

export const EmailInput = component$<EmailInputProps>((props) => {
  const field = useField$(props.of, { path: ['email'] });

  return (
    <div>
      <label>
        Email
        <input {...field.props} value={field.input.value ?? ''} type="email" />
      </label>
      {field.errors.value && <div>{field.errors.value[0]}</div>}
    </div>
  );
});

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.

Contributors

Thanks to all the contributors who helped make this page better!

  • GitHub profile picture of @fabian-hiller

Partners

Thanks to our partners who support the project ideally and financially.

Sponsors

Thanks to our GitHub sponsors who support the project financially.

  • GitHub profile picture of @vasilii-kovalev
  • GitHub profile picture of @saturnonearth
  • GitHub profile picture of @ruiaraujo012
  • GitHub profile picture of @hyunbinseo
  • GitHub profile picture of @nickytonline
  • GitHub profile picture of @KubaJastrz
  • GitHub profile picture of @andrewmd5
  • GitHub profile picture of @Thanaen
  • GitHub profile picture of @caegdeveloper
  • GitHub profile picture of @bmoyroud
  • GitHub profile picture of @dslatkin