Formify
A modern, TypeScript-first form library for React — everything Formik has, plus what Formik doesn't
Formify
A modern, TypeScript-first form library for React — everything Formik has, plus what Formik doesn't.
Formify is a drop-in upgrade over Formik with:
- ✅ Full API parity with Formik —
useForm,<Formify>,<Form>,<Field>,<FastField>,<ErrorMessage>,<FieldArray>,useField,useFormifyContext,connect,withFormify,getFieldProps,setFieldValue,validateForm,resetForm, Yup interop and more. - ✅ Config-driven validation schemas — declare validation as plain data:
{ email: { required: true, email: true } }. No boilerplate. - ✅ 35+ built-in validators — email, URL, phone, UUID, IP, credit card (Luhn), SSN, EIN, ZIP, domain, slug, ISBN, strong password, date, time, min/max length, ranges, oneOf, confirm (password match), and more — each with configurable messages.
- ✅ Async validation built in — debounced, cancellable API checks (e.g. "username already taken") via
uniqueCheck()or customasyncValidate, with stale-response protection and per-field loading flags. - ✅ Plugin system — hook into the form lifecycle: normalize input, add validation rules, block submits, react to errors — plus ready-made plugins:
autosavePlugin,honeypotPlugin,confirmDirtyPlugin,passwordStrengthPlugin,formatPlugin. Nothing Formik can do, that Formify can't. - ✅ Input normalization —
trim,uppercase,lowercase,digits,alphanumericor custom transforms applied as the user types. - ✅ Input formatters — SSN, EIN, ITIN/TIN masks, country-aware phone numbers (with/without extension), ZIP, credit card, currency, date, time — applied on change or on blur, all customizable.
- ✅ Beyond Formik — per-field
validateOnChange/validateOnBluroverrides, conditional validation (when), dependent-field revalidation (deps), focus-first-error on submit, global message dictionaries (i18n), debounce config. - ✅ TypeScript-first — full type safety and autocomplete, dual ESM + CJS builds, zero runtime dependencies (React peer only).
v1 → v2? The old
useFormValidator(initialValues, schema, onSubmit)API andregexValhelpers still work — see Migration from v1.
Installation
npm install formify-js
# or
yarn add formify-js
# or
pnpm add formify-jsRequires react >= 18. Formify has zero runtime dependencies (React peer only).
Quick start
import { useForm, V } from "formify-js";
interface SignupValues {
email: string;
password: string;
}
export function Signup() {
const form = useForm<SignupValues>({
initialValues: { email: "", password: "" },
validationSchema: {
email: { required: true, email: true },
password: { required: true, minLength: 8, strongPassword: true },
},
onSubmit: async (values) => {
await api.signup(values);
},
});
return (
<form onSubmit={form.handleSubmit}>
<input {...form.getFieldProps("email")} type="email" />
{form.touched.email && form.errors.email && (
<p className="error">{form.errors.email}</p>
)}
<input {...form.getFieldProps("password")} type="password" />
{form.touched.password && form.errors.password && (
<p className="error">{form.errors.password}</p>
)}
<button type="submit" disabled={form.isSubmitting}>
{form.isSubmitting ? "Signing up…" : "Sign up"}
</button>
</form>
);
}Or with components (Formik-compatible API, Formify names):
import { Formify, Form, Field, ErrorMessage } from "formify-js";
<Formify
initialValues={{ email: "", password: "" }}
validationSchema={{
email: { required: true, email: true },
password: { required: true, minLength: 8 },
}}
onSubmit={async (values) => api.signup(values)}
>
<Form>
<Field name="email" type="email" placeholder="you@example.com" />
<ErrorMessage name="email" component="p" />
<Field name="password" type="password" />
<ErrorMessage name="password" component="p" />
<button type="submit">Sign up</button>
</Form>
</Formify>Async validation (unique checks)
import { useForm, uniqueCheck } from "formify-js";
const form = useForm({
initialValues: { username: "" },
validationSchema: {
username: {
required: true,
minLength: 3,
asyncValidate: uniqueCheck(
async (value, { signal }) => {
const res = await fetch(`/api/users/exists?username=${value}`, { signal });
return res.json(); // true = taken
},
{ message: "This username is already taken", debounce: 400 },
),
},
},
onSubmit: (values) => api.createUser(values),
});
// Show a spinner while the check is running:
{form.validating.username && <Spinner />}Stale responses are cancelled automatically via AbortSignal — typing "joh" then "john" only ever shows the result for "john".
Features at a glance
| Area | Formik | Formify |
|---|---|---|
Hooks (useForm) | ✅ | ✅ (plus extras) |
Components (Formify, Form, Field, FastField, ErrorMessage, FieldArray) | ✅ | ✅ |
HOC (withFormify, connect) | ✅ | ✅ |
| Yup schemas | ✅ (peer dep) | ✅ (duck-typed, optional) |
| Config-driven schemas | ❌ | ✅ |
| Built-in validators with messages | ❌ | ✅ (35+) |
| Input formatters (SSN, EIN, phone, …) | ❌ | ✅ |
| Trim modes (start / end / both) on change or blur | ❌ | ✅ |
| Async validation with debounce + cancellation | ❌ | ✅ |
Unique/API checks (uniqueCheck) | ❌ | ✅ |
| Input normalization | ❌ | ✅ |
| Plugin system | ❌ | ✅ |
Conditional validation (when) | ❌ | ✅ |
Dependent-field revalidation (deps) | ❌ | ✅ |
| Per-field validateOnChange / validateOnBlur | ❌ | ✅ |
| Focus first invalid field on submit | ❌ | ✅ |
| Global message dictionary (i18n) | ❌ | ✅ |
| TypeScript-first, dual ESM/CJS | partial | ✅ |
Test app
The repo ships a Vite test app that exercises every feature live — signup with async unique checks, formatters, plugins, FieldArray, conditional + i18n, Yup interop, and a performance benchmark (per-keystroke latency, full-form validation and submit cost at 10 / 50 / 100 / 250 fields).
cd app
npm install
npm run dev # http://localhost:5173
npm run build # production build