Skip to content
VersionSizeTypeScriptDependencies
Formit logo

Formit

@vielzeug/formit is a typed, framework-agnostic form controller for values, validation, dirty/touched state, and submission orchestration.

Installation

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

Quick Start

ts
import { createForm, FormValidationError } from '@vielzeug/formit';

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();

try {
  await form.submit(async (values) => {
    await fetch('/api/login', {
      body: JSON.stringify(values),
      headers: { 'Content-Type': 'application/json' },
      method: 'POST',
    });
  });
} catch (error) {
  if (error instanceof FormValidationError) {
    console.log(error.errors);
  }
}

Why Formit?

Rolling form state by hand means recreating the same touched, dirty, and errors tracking for every form. Most form libraries that handle this are tightly coupled to a specific framework.

ts
// Before — manual form state
let values = { name: '', email: '' };
let errors: Record<string, string> = {};
let touched: Record<string, boolean> = {};
async function validate() {
  errors = {};
  if (!values.name) errors.name = 'Required';
  if (!values.email.includes('@')) errors.email = 'Invalid email';
  return Object.keys(errors).length === 0;
}

// After — Formit
import { createForm } from '@vielzeug/formit';
const form = createForm({
  defaultValues: { name: '', email: '' },
  validators: {
    name: (v) => (!v ? 'Required' : undefined),
    email: (v) => (!String(v).includes('@') ? 'Invalid email' : undefined),
  },
});
const { valid, errors } = await form.validate();
FeatureFormitReact Hook FormFormik
Bundle size2.1 KB~15 kB~17 kB
Framework agnosticReact onlyReact only
Typed field values❌ (strings)
Async validators
AbortSignal
Field arrays
Zero dependencies

Use Formit when you need framework-agnostic form state management with typed field values, async validation, and fine-grained subscriptions.

Consider React Hook Form if you are in a React project and want its uncontrolled-component performance model and large plugin ecosystem.

Features

  • Typed paths with dot-notation inference (user.profile.name)
  • Value store + baseline tracking for reliable dirty state
  • Field + form validators with async support and abort signals
  • Partial validation without clobbering unrelated errors
  • Submit flow with SubmitError and FormValidationError
  • Form and field subscriptions (subscribe, watch)
  • Memoized input bindings (bind)
  • Array helpers for dynamic list fields
  • FormData conversion via instance and standalone helpers
  • Zero dependencies2.1 KB gzipped

Compatibility

EnvironmentSupport
Browser
Node.js❌ (DOM only)
SSR❌ (DOM only)
Deno

Prerequisites

  • Browser runtime for form element events and FormData workflows.
  • Define defaultValues with the same shape you submit to your API.
  • Pair with @vielzeug/validit (optional) when you need reusable schema-based validation.

See Also