Skip to content
forge logoForgeForms
Framework-agnostic typed form state with path-safe fields, unified validation API, deterministic submit flow, and browser-friendly helpers.
v0.0.15.5 KB gzip Browser · Node.js · SSR · Deno
createFormtoFormDataValidationModesFORM_ERROR

Why Forge?

Native form handling quickly grows repetitive when you need typed values, deterministic submit behavior, and granular subscriptions.

ts
// Before: manual state and ad-hoc validation sequencing
const errors: Record<string, string> = {};
if (!email.includes('@')) errors.email = 'Invalid email';
if (password.length < 8) errors.password = 'Too short';
if (Object.keys(errors).length === 0) await submit({ email, password });

// After: one form controller with explicit transitions
const form = createForm({
  defaultValues: { email: '', password: '' },
  validators: { email: isEmail, password: min8 },
});
await form.submit(submit);
FeatureForgeReact Hook FormVeeValidate
Bundle size5.5 KB~9 kB~16 kB
Framework-agnosticReact onlyVue only
Typed dot-path APIsPartialPartial
Result-based submit flow
Live field observation
Full array helpers
Scoped sub-forms
Form + field subscriptions
Zero dependencies

Use Forge when you want one typed form controller that works across frameworks or in vanilla apps with explicit, predictable state transitions.

Consider framework-specific alternatives when you need deeply integrated framework bindings and are not sharing form logic across runtimes.

Installation

sh
pnpm add @vielzeug/forge
sh
npm install @vielzeug/forge
sh
yarn add @vielzeug/forge

Quick Start

ts
import { createForm } from '@vielzeug/forge';

const form = createForm({
  defaultValues: { email: '', password: '' },
  validators: {
    email: (v) => (!String(v).includes('@') ? 'Invalid email' : undefined),
    password: (v) => (String(v).length < 8 ? 'Min 8 chars' : undefined),
  },
});

const { valid, errors } = await form.validate();

if (!valid) {
  console.log(errors);
}

const submission = await form.submit(async (values) => {
  await fetch('/api/login', {
    body: JSON.stringify(values),
    headers: { 'Content-Type': 'application/json' },
    method: 'POST',
  });
});

if (!submission.ok && submission.type === 'validation') {
  console.log(submission.errors);
}

Features

  • Typed field paths with compile-time value inference
  • Explicit validation API: validate() and validateFields(fields)
  • Single-field validation with validateField(name)
  • Streaming validation with validateStream() — yields each field result as it resolves, read-only
  • Per-connection validation triggers via connect() with ValidationModes presets
  • connect() bindings own independent debounce timers; call disconnect() on unmount
  • submit(handler) — returns { ok: true, value } or { ok: false, errors }
  • Schema integration: pass any safeParse-compatible schema directly to validator
  • scope(prefix) — memoized scoped sub-forms that share parent state with relative field paths
  • subscribeScoped() — filtered subscription that only fires on changes within the scope's prefix
  • snapshot() / restore() — capture and replay complete form state
  • removeField(name) — clean conditional field lifecycle
  • Full array helpers: append, prepend, insert, remove, move, swap, replace
  • Explicit synchronous subscriptions: subscribe, subscribeField, and subscribeScoped
  • Stable frozen snapshots for form.state and form.field(name) (external-store friendly)
  • Explicit touched and error controls: touch, untouch, touchAll, untouchAll, setError, resetErrors
  • Mutation batching with batch(fn) and dynamic field validators via setValidator
  • Baseline-safe reset/replace/patch model
  • Browser-first utility: toFormData
  • Ready-made adapters for React, Vue, and Svelte
  • @vielzeug/forge/validators adapter: fieldValidator and composeValidators

Documentation

See Also

  • Spell — schema validation; plug a Spell schema into Forge to validate fields and submission payloads with full type inference
  • Courier — HTTP client; submit Forge's validated payload directly through a Courier mutation with loading and error state wired automatically
  • Ripple — reactive signals; Forge exposes field values and submission state as signals for fine-grained reactive UI updates