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.

<script setup lang="ts">
import { Field, Form, useForm } from '@formisch/vue';
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 loginForm = useForm({
  schema: LoginSchema,
});

// TypeScript knows the form structure from the schema
// loginForm is of type FormStore<typeof LoginSchema>
</script>

<template>
  <Form
    :of="loginForm"
    @submit="
      (output) => {
        // output is fully typed based on LoginSchema
        console.log(output.email); // ✓ Type-safe
        console.log(output.username); // ✗ TypeScript error
      }
    "
  >
    <!-- Form fields -->
  </Form>
</template>

Input and output types

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

<script setup lang="ts">
import { Field, Form, useForm } from '@formisch/vue';
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()
  ),
});

const profileForm = useForm({
  schema: ProfileSchema,
});
</script>

<template>
  <Form
    :of="profileForm"
    @submit="
      (output) => {
        // output is: { age: number; birthDate: Date }
        console.log(output.age); // number
        console.log(output.birthDate); // Date
      }
    "
  >
    <Field :of="profileForm" :path="['age']" v-slot="field">
      <!-- field.input is string -->
      <input v-bind="field.props" v-model="field.input" type="text" />
    </Field>

    <Field :of="profileForm" :path="['birthDate']" v-slot="field">
      <!-- field.input is string -->
      <input v-bind="field.props" v-model="field.input" type="date" />
    </Field>

    <button type="submit">Submit</button>
  </Form>
</template>

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']" v-slot="field" />
<Field :of="userForm" :path="['profile', 'email']" v-slot="field" />

// ✗ Invalid paths - TypeScript error
<Field :of="userForm" :path="['profile', 'age']" v-slot="field" />
<Field :of="userForm" :path="['username']" v-slot="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.

<script setup lang="ts">
import { Form, type FormStore, useForm } from '@formisch/vue';
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 loginForm = useForm({
  schema: LoginSchema,
});
</script>

<template>
  <FormContent :of="loginForm" />
</template>

<script setup lang="ts">
type FormContentProps = {
  of: FormStore<typeof LoginSchema>;
};

defineProps<FormContentProps>();
</script>

<template>
  <Form :of="props.of" @submit="(output) => console.log(output)">
    <!-- Form fields -->
  </Form>
</template>

Generic field components

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

<script
  setup
  lang="ts"
  generic="TSchema extends v.GenericSchema<{ email: string }>"
>
import { useField } from '@formisch/vue';
import type { FormStore } from '@formisch/vue';
import * as v from 'valibot';

export interface EmailInputProps<
  TSchema extends v.GenericSchema<{ email: string }>,
> {
  of: FormStore<TSchema>;
}

const props = defineProps<EmailInputProps<TSchema>>();

const field = useField(
  () => props.of,
  () => ({ path: ['email'] })
);
</script>

<template>
  <div>
    <label>
      Email
      <input v-bind="field.props" v-model="field.input" type="email" />
    </label>
    <div v-if="field.errors">{{ field.errors[0] }}</div>
  </div>
</template>

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/vue. 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