nuxt-formisch
nuxt-formisch

Formisch integration: schema-based, headless form library

Nuxt Formisch: type-safe forms for Nuxt

Nuxt Formisch

Type-safe, headless forms for Nuxt with Formisch and Valibot, plus an optional Nuxt UI AutoForm.

nuxt-formisch versionnuxt-formisch downloadsDocsLicense

Formisch is a small, schema-based form library: you describe a form with a Valibot schema and get typed values, field state and validation. This module auto-imports its composables, components and methods, validates request bodies in server routes with the same schema, and, with Nuxt UI installed, renders a whole form from a schema with <FAutoForm>.

Quick start

pnpm add nuxt-formisch valibot
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['nuxt-formisch'],
})

Define the schema in shared/utils/, where both the app and the server auto-import it:

// shared/utils/schemas.ts
import * as v from 'valibot'

export const LoginSchema = v.object({
  email: v.pipe(v.string(), v.email()),
  password: v.pipe(v.string(), v.minLength(8)),
})

Build the form. useForm, <FForm> and <FField> are auto-imported:

<!-- app/pages/login.vue -->
<script setup lang="ts">
const form = useForm({ schema: LoginSchema })

const login: SubmitHandler<typeof LoginSchema> = values =>
  $fetch('/api/login', { method: 'POST', body: values })
</script>

<template>
  <FForm :of="form" @submit="login">
    <FField v-slot="field" :of="form" :path="['email']">
      <input v-model="field.input" v-bind="field.props" type="email">
      <p v-if="field.errors">{{ field.errors[0] }}</p>
    </FField>
    <FField v-slot="field" :of="form" :path="['password']">
      <input v-model="field.input" v-bind="field.props" type="password">
      <p v-if="field.errors">{{ field.errors[0] }}</p>
    </FField>
    <button :disabled="form.isSubmitting">Log in</button>
  </FForm>
</template>

Validate the same body on the server. readValidatedFormBody returns the typed output or throws a 400 with the issues:

// server/api/login.post.ts
export default defineEventHandler(async (event) => {
  const { email } = await readValidatedFormBody(event, LoginSchema)
  return { email }
})

With Nuxt UI in modules, <FAutoForm> renders the inputs, labels, errors and submit button from the schema:

<FAutoForm :schema="LoginSchema" @submit="login" />

Docs

Develop

pnpm install
pnpm dev:prepare    # stub the module and prepare the playground
pnpm dev            # the playground in playground/
pnpm test
pnpm lint
pnpm typecheck
pnpm check:doctor   # vite-doctor
pnpm validate       # build the module and run publint

The docs site in docs/ is a separate project with its own lockfile:

cd docs
pnpm install
pnpm dev
pnpm og             # redraw docs/public/og/*.png

Release

Releases are cut by uppt from conventional commits. Every push to main opens or updates a draft release PR with the next version and changelog. Merging it tags the release and publishes the package to npm.

License

MIT