{"id":49260502,"url":"https://github.com/cubicforms/chemical-x-forms","last_synced_at":"2026-04-25T07:01:34.814Z","repository":{"id":274735222,"uuid":"923653977","full_name":"cubicforms/chemical-x-forms","owner":"cubicforms","description":"A fully type-safe, schema-driven form library that gives you superpowers. Chemical X included.","archived":false,"fork":false,"pushed_at":"2026-04-23T05:49:53.000Z","size":743,"stargazers_count":5,"open_issues_count":43,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-04-23T06:24:43.547Z","etag":null,"topics":["composables","form","forms","nuxt3","package","register","schemas","vue3","xmodel","zod"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cubicforms.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-01-28T16:25:50.000Z","updated_at":"2026-04-14T17:14:02.000Z","dependencies_parsed_at":"2025-02-12T20:26:29.386Z","dependency_job_id":"ea008685-0122-4147-8dab-ef46a5dbe959","html_url":"https://github.com/cubicforms/chemical-x-forms","commit_stats":null,"previous_names":["cubicforms/chemical-x-forms"],"tags_count":67,"template":false,"template_full_name":null,"purl":"pkg:github/cubicforms/chemical-x-forms","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cubicforms%2Fchemical-x-forms","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cubicforms%2Fchemical-x-forms/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cubicforms%2Fchemical-x-forms/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cubicforms%2Fchemical-x-forms/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cubicforms","download_url":"https://codeload.github.com/cubicforms/chemical-x-forms/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cubicforms%2Fchemical-x-forms/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32253251,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-25T04:23:17.126Z","status":"ssl_error","status_checked_at":"2026-04-25T04:21:53.360Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["composables","form","forms","nuxt3","package","register","schemas","vue3","xmodel","zod"],"created_at":"2026-04-25T07:01:30.238Z","updated_at":"2026-04-25T07:01:34.654Z","avatar_url":"https://github.com/cubicforms.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Chemical X Forms\n\n[![npm version][npm-version-src]][npm-version-href]\n[![npm downloads][npm-downloads-src]][npm-downloads-href]\n[![License][license-src]][license-href]\n[![Node.js Test Suite](https://github.com/cubicforms/chemical-x-forms/actions/workflows/matrix.yml/badge.svg)](https://github.com/cubicforms/chemical-x-forms/actions/workflows/matrix.yml)\n[![Nuxt][nuxt-src]][nuxt-href]\n\n**A fully type-safe, schema-driven form library that gives you superpowers**.\u003cbr\u003eComes with a minimal composition API that prioritizes developer experience and form correctness.\u003cbr\u003e\u003cbr\u003e\n\n## 🚀 60-second start\n\n```bash\nnpm install @chemical-x/forms zod\n```\n\n**Nuxt 3 / 4** — add the module:\n\n```ts\n// nuxt.config.ts\nexport default defineNuxtConfig({\n  modules: ['@chemical-x/forms/nuxt'],\n})\n```\n\n**Bare Vue 3** — install the plugin:\n\n```ts\n// main.ts\nimport { createApp } from 'vue'\nimport { createChemicalXForms } from '@chemical-x/forms'\n\ncreateApp(App).use(createChemicalXForms()).mount('#app')\n```\n\n```ts\n// vite.config.ts\nimport vue from '@vitejs/plugin-vue'\nimport { chemicalXForms } from '@chemical-x/forms/vite'\n\nexport default defineConfig({\n  plugins: [vue(), chemicalXForms()],\n})\n```\n\nThat's it. [Jump to your first form →](#-your-first-form)\n\u003cbr\u003e\u003cbr\u003e\n\n## 🪄 Your first form\n\n```vue\n\u003cscript setup lang=\"ts\"\u003e\n  import { useForm } from '@chemical-x/forms/zod' // zod v4; use /zod-v3 for v3\n  import { z } from 'zod'\n\n  const { register, handleSubmit, fieldErrors, state } = useForm({\n    schema: z.object({\n      email: z.email(),\n      password: z.string().min(8),\n    }),\n  })\n\n  const onSubmit = handleSubmit(async (values) =\u003e {\n    await fetch('/api/signup', { method: 'POST', body: JSON.stringify(values) })\n  })\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cform @submit.prevent=\"onSubmit\"\u003e\n    \u003cinput v-register=\"register('email')\" placeholder=\"Email\" /\u003e\n    \u003csmall v-if=\"fieldErrors.email?.[0]\"\u003e{{ fieldErrors.email[0].message }}\u003c/small\u003e\n\n    \u003cinput v-register=\"register('password')\" type=\"password\" placeholder=\"Password\" /\u003e\n    \u003csmall v-if=\"fieldErrors.password?.[0]\"\u003e{{ fieldErrors.password[0].message }}\u003c/small\u003e\n\n    \u003cbutton :disabled=\"state.isSubmitting\"\u003eSign up\u003c/button\u003e\n  \u003c/form\u003e\n\u003c/template\u003e\n```\n\nYou get: schema-typed values, per-field errors, a submit handler that\nvalidates first, and a reactive `state` bundle (`isSubmitting`,\n`isDirty`, `isValid`, and six more — see below). Every leaf of\n`fieldErrors` and every branded path is inferred from your Zod schema.\n\u003cbr\u003e\u003cbr\u003e\n\n## 🎯 The core you always have\n\nEverything below is on by default — no opt-in needed:\n\n- **`register(path)` + `v-register`** — bind an input to a field in one directive. SSR-safe, no per-input `v-model` + `@input` boilerplate.\n- **`handleSubmit(onSubmit, onError?)`** — validates, then dispatches. Bind straight to `@submit.prevent`.\n- **`fieldErrors`** — reactive `Record\u003cpath, ValidationError[]\u003e`. Auto-populated by `handleSubmit` on failure, cleared on success. Also writable from your own code.\n- **`state`** — reactive bundle of form-level flags and counters: `state.isDirty` / `state.isValid` (gate a \"Save\" button on `state.isDirty \u0026\u0026 state.isValid` without wiring per-field watchers), `state.isSubmitting` / `state.submitCount` / `state.submitError` (full submission lifecycle — spinner, per-click counter, reactive error banner with zero extra refs), `state.isValidating` (async-validation flag), and `state.canUndo` / `state.canRedo` / `state.historySize` (undo/redo, always present; inert when `history` is off). Auto-unwraps in templates — no `.value`.\n- **`getValue(path)` / `setValue(path, value)`** — read / write any field programmatically.\n- **`getFieldState(path)`** — everything for one path: value, errors, touched, focused, blurred, isConnected, updatedAt.\n- **`reset(next?)` / `resetField(path)`** — restore the whole form, or a single subtree, back to schema defaults (or a partial override).\n- **Field-array helpers** — `append` / `prepend` / `insert` / `remove` / `swap` / `move` / `replace`. Path is narrowed to arrays, value to the element type — `append('title', …)` on a string field is a compile error. [Recipe →](./docs/recipes/dynamic-field-arrays.md)\n- **Structured paths** — field names with literal dots? `register(['user.name'])` keeps them as a single segment. `register('user.name')` splits.\n  \u003cbr\u003e\u003cbr\u003e\n\n## ⚡ Superpowers (opt-in)\n\nFlip a config flag, get a whole feature. Each of these is off by default.\n\n### Async validation\n\nUse `z.refine(async …)` to check uniqueness, allow-lists, server availability. `handleSubmit` awaits it for you.\n\n```ts\nconst schema = z.object({\n  email: z.email().refine(async (v) =\u003e !(await isEmailTaken(v)), 'Email already registered'),\n})\n```\n\n`validate()` / `validateAsync(path?)` / `state.isValidating` give you reactive + imperative surfaces for live validation UI. [Recipe →](./docs/recipes/async-validation.md)\n\n### Live field validation\n\nValidate as the user types or tabs away — no submit needed:\n\n```ts\nuseForm({ schema, fieldValidation: { on: 'change', debounceMs: 200 } })\n```\n\nThree modes — `'change'` (debounced), `'blur'` (immediate), `'none'` (default). Rapid typing is debounced + auto-cancelled. [Recipe →](./docs/recipes/field-level-validation.md)\n\n### Focus / scroll to first error\n\n```ts\nuseForm({ schema, onInvalidSubmit: 'focus-first-error' })\n```\n\nOr call `focusFirstError()` / `scrollToFirstError({ block: 'start' })` imperatively after a failed submit or a `setFieldErrorsFromApi` hydration. [Recipe →](./docs/recipes/focus-on-error.md)\n\n### Persist drafts across reloads\n\n```ts\nuseForm({ schema, key, persist: { storage: 'local' } })\n```\n\nBackends: `'local'` / `'session'` / `'indexeddb'` (or your own). Writes debounced, clears on successful submit, survives hard refresh. [Recipe →](./docs/recipes/persistence.md)\n\n### Undo / redo\n\n```ts\nuseForm({ schema, key, history: true })\n```\n\nAdds `undo()` / `redo()` methods plus `state.canUndo` / `state.canRedo` / `state.historySize` on a bounded snapshot stack (default 50). Wire it to \u003ckbd\u003e⌘Z\u003c/kbd\u003e / \u003ckbd\u003e⌘⇧Z\u003c/kbd\u003e in one line. [Recipe →](./docs/recipes/undo-redo.md)\n\n### Nested form components\n\nCall `useFormContext()` in any descendant to reach the ancestor's form without prop-threading. Pass a form's `key` to reach a form that isn't an ancestor — or when a single parent owns more than one form and descendants need to disambiguate. [Recipe →](./docs/recipes/form-context.md)\n\n### Server errors\n\n```ts\nsetFieldErrorsFromApi(err.data) // accepts { error: { details: { path: [msg] } } } or { path: [msg] }\n```\n\nDrops straight into your `catch` block. Built-in caps on entry count + path depth keep untrusted payloads safe. [Recipe →](./docs/recipes/server-errors.md)\n\n### Vue DevTools\n\n```bash\nnpm install -D @vue/devtools-api\n```\n\nEvery registered form shows up in the DevTools sidebar with an editable tree, an error view, and a timeline for submit / reset / mutation events. Auto-wired; pass `createChemicalXForms({ devtools: false })` to disable. [Recipe →](./docs/recipes/devtools.md)\n\n### SSR\n\nNuxt: zero config — the module handles payload round-trip via `nuxtApp.payload`.\u003cbr\u003e\nBare Vue + `@vue/server-renderer`: `renderChemicalXState(app)` on the server, `hydrateChemicalXState(app, payload)` on the client. [Recipe →](./docs/recipes/ssr-hydration.md)\n\n### Bring your own schema library\n\nZod v4 is the default. Valibot, ArkType, hand-rolled — implement four methods on `AbstractSchema` and `useForm` works against it. [Recipe →](./docs/recipes/custom-adapter.md)\n\u003cbr\u003e\u003cbr\u003e\n\n## 📚 Documentation\n\n- [**`docs/api.md`**](./docs/api.md) — every public export with signatures and return shapes\n- [**`docs/recipes/`**](./docs/recipes) — task-oriented walkthroughs for everything above\n- [**`docs/troubleshooting.md`**](./docs/troubleshooting.md) — common gotchas and fixes\n- [**`docs/migration/`**](./docs/migration) — per-release upgrade notes\n- [**`docs/perf.md`**](./docs/perf.md) — how it scales; when to worry\n- [**`CHANGELOG.md`**](./CHANGELOG.md) — full release history\n  \u003cbr\u003e\u003cbr\u003e\n\n## 🏔️ What's in the box\n\n- **Framework-agnostic core** — Nuxt 3 / 4, bare Vue 3 (CSR), bare Vue 3 + `@vue/server-renderer` (SSR). One Vue plugin; the Nuxt module wraps it.\n- **Schema-agnostic, Zod-friendly** — Zod v4 at `/zod`, Zod v3 at `/zod-v3`. Bring your own validator if you don't use Zod.\n- **TypeScript-first** — every strictness flag on, branded `PathKey` / `FormKey`, no `any` in the public surface.\n- **Performance** — keystroke path is 6–12× faster than the pre-rewrite baseline; a CI job fails the run if the ratio drops.\n- **Zero framework-specific validator ceremony** — no `v-model` + `@input` wiring, no manual error mapping from your schema library to your UI.\n  \u003cbr\u003e\u003cbr\u003e\n\n## 📦 Status\n\n**Pre-1.0.** The API is stable and under SemVer from `v1.0` onward —\n0.x minor bumps may still include small breaking changes; each one\nlands with a migration note under [`docs/migration/`](./docs/migration). [Recent changes →](./CHANGELOG.md)\n\u003cbr\u003e\u003cbr\u003e\n\n### Subpath exports\n\n| Subpath                        | Purpose                                                |\n| ------------------------------ | ------------------------------------------------------ |\n| `@chemical-x/forms`            | Framework-agnostic core (plugin, `useForm`, directive) |\n| `@chemical-x/forms/nuxt`       | Nuxt 3 / 4 module                                      |\n| `@chemical-x/forms/vite`       | Vite plugin (registers node transforms)                |\n| `@chemical-x/forms/transforms` | Raw node transforms for custom bundlers                |\n| `@chemical-x/forms/zod`        | Zod v4 adapter (recommended; requires `zod@^4`)        |\n| `@chemical-x/forms/zod-v3`     | Zod v3 adapter (legacy; requires `zod@^3`)             |\n\n\u003cbr\u003e\n\n## 🪪 License\n\n`@chemical-x/forms` is released under the MIT License. See the [LICENSE](https://github.com/cubicforms/chemical-x-forms/blob/main/LICENSE) file for details.\n\n\u003c!-- Badges --\u003e\n\n[npm-version-src]: https://img.shields.io/npm/v/@chemical-x/forms/latest.svg?style=flat\u0026colorA=020420\u0026colorB=00DC82\n[npm-version-href]: https://npmjs.com/package/@chemical-x/forms\n[npm-downloads-src]: https://img.shields.io/npm/dm/@chemical-x/forms.svg?style=flat\u0026colorA=020420\u0026colorB=00DC82\n[npm-downloads-href]: https://npm.chart.dev/@chemical-x/forms\n[license-src]: https://img.shields.io/npm/l/@chemical-x/forms.svg?style=flat\u0026colorA=020420\u0026colorB=00DC82\n[license-href]: https://npmjs.com/package/@chemical-x/forms\n[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js\n[nuxt-href]: https://nuxt.com\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcubicforms%2Fchemical-x-forms","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcubicforms%2Fchemical-x-forms","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcubicforms%2Fchemical-x-forms/lists"}