@chemical-x/forms

A fully type-safe, schema-driven form library that gives you superpowers. Chemical X included.
4,860 6
Install
npm install @chemical-x/forms
Latest Version:0.13.0
Vue:3
License:MIT
Last Updated:Oct 19, 2026
Links: GitHub  ·  npm
Maintainer: ozzyfromcubic

Chemical X Forms

npm version npm downloads License Node.js Test Suite Nuxt

A schema-driven form library for Vue 3 and Nuxt. Bring a Zod schema (or your own validator); useForm returns typed reads, writes, errors, and a submit handler. Every path, value, and error is inferred from the schema — no any, no string keys.

Installation

npm install @chemical-x/forms zod

Nuxt 3 / 4 — install the module:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@chemical-x/forms/nuxt'],
})

Bare Vue 3 — install the plugin and the Vite plugin:

// main.ts
import { createApp } from 'vue'
import { createChemicalXForms } from '@chemical-x/forms'

createApp(App).use(createChemicalXForms()).mount('#app')
// vite.config.ts
import vue from '@vitejs/plugin-vue'
import { chemicalXForms } from '@chemical-x/forms/vite'

export default defineConfig({
  plugins: [vue(), chemicalXForms()],
})

Quick start

<script setup lang="ts">
  import { z } from 'zod'
  import { useForm } from '@chemical-x/forms/zod' // zod v4; use /zod-v3 for v3

  const schema = z.object({
    email: z.email(),
    password: z.string().min(8),
  })

  const form = useForm({ schema, key: 'signup' })

  const onSubmit = form.handleSubmit(async (values) => {
    await $fetch('/api/signup', { method: 'POST', body: JSON.stringify(values) })
  })
</script>

<template>
  <form @submit.prevent="onSubmit">
    <input v-register="form.register('email')" placeholder="Email" />
    <small v-if="form.errors.email?.[0]">{{ form.errors.email[0].message }}</small>

    <input v-register="form.register('password')" type="password" placeholder="Password" />
    <small v-if="form.errors.password?.[0]">{{ form.errors.password[0].message }}</small>

    <button :disabled="form.state.isSubmitting">Sign up</button>
  </form>
</template>

useForm({ schema, key }) returns a Pinia-style reactive object — read leaves directly, no .value:

  • form.values — current values. form.values.email, form.values.address.city.
  • form.errors — per-field errors, keyed by dotted path. form.errors.email?.[0]?.message.
  • form.fieldState — per-field flags (dirty, touched, errors, blank, …). form.fieldState.email.dirty.
  • form.state — form-level flags (isSubmitting, isValid, canUndo, …).
  • form.register(path) — typed two-way binding; pair with v-register on <input> / <textarea> / <select>.
  • form.handleSubmit(onValid, onInvalid?) — runs validation, dispatches. The valid callback receives the strict zod-inferred type.
  • form.setValue(path, value), form.reset(), field-array helpers, undo / redo, persistence — see the API reference.

Features

  • Schema-driven types — every path, value, and error is inferred from the schema; no any.
  • Live validation — debounced 'change' by default; 'blur' and 'none' available; async refinements await before submit dispatches.
  • Field arrays — append / prepend / insert / remove / swap / move / replace, fully typed at the call site.
  • Drafts + undo / redo — per-field opt-in persistence (localStorage / sessionStorage / IndexedDB / custom backend) and a bounded undo stack.
  • Server errors — parseApiErrors(payload) normalises a { message, code }[] wire format; pair with form.setFieldErrors(...). User errors survive schema revalidation.
  • Stable error codes — every ValidationError carries code: string. Library codes (cx:) live on the exported CxErrorCode enum; adapter codes use a zod: prefix; consumers pick their own (api:, auth:, …).
  • Clearable required fields — the unset sentinel marks a field displayed-empty while storage holds the schema's slim default. Submit fails with 'No value supplied' for required schemas; .optional() / .nullable() / .default(N) opt out.
  • SSR — Nuxt handles the payload round-trip automatically; bare Vue uses renderChemicalXState / hydrateChemicalXState (recipe).

Documentation

Status

Pre-1.0. SemVer applies from v1.0 onward; 0.x minor bumps may still include breaking changes, each documented under docs/migration/.

License

MIT — see LICENSE.

Related Packages

nuxt-app-config-plus

Create nested App Config using a folder structure.

11,545 11
nuxt-open-fetch

Generate zero-overhead, 100% typed OpenAPI clients for Nuxt.

379,588 259
vue-use-state-effect

Fast and small library built on top of the native Vue.js API, providing a safe a...

13,117 38
vue-use-variant

Vue.js CSS class variant resolver. Presented as handy composable.

3,577 36
vue3-use-route-query

A typed Vue 3 composable for synchronizing reactive values with Vue Router query...

3,031 2

Version History

Version Released Vue License
0.13.0 >=3.5.0 MIT
0.12.1 >=3.5.0 MIT
0.12.0 >=3.5.0 MIT
0.11.1 >=3.5.0 MIT
0.11.0 >=3.5.0 MIT