A form-agnostic React library — one API for React Hook Form, Formik, and TanStack Form
Write your form components once, swap the underlying engine by changing a single import.
- Form Agnostic — Switch between React Hook Form, Formik, and TanStack Form without rewriting components
- Schema-First Validation — Built-in Zod support with extensible schema bridge architecture
- Full TypeScript Support — Complete type definitions with intelligent autocomplete
- Tiny Footprint — Minimal overhead on top of your chosen form library
- Dual API — Use hooks (
useUniversalForm) or components (<Field />) - Field Arrays — Built-in support for dynamic lists with stable keys
- SSR Compatible — Works with Next.js, Remix, and other SSR frameworks
npm install @samithahansaka/formless
# Plus your form library of choice
npm install react-hook-form # or formik, or @tanstack/react-form
npm install zod # for schema validation# Core packages
npm install @samithahansaka/formless-core @samithahansaka/formless-react @samithahansaka/formless-zod
# Choose your form engine adapter
npm install @samithahansaka/formless-react-hook-form react-hook-form
# or
npm install @samithahansaka/formless-formik formik
# or
npm install @samithahansaka/formless-tanstack-form @tanstack/react-formimport { z } from 'zod';
import {
useUniversalForm,
Field,
rhfAdapter, // or formikAdapter, tanstackAdapter
zodBridge,
} from '@samithahansaka/formless';
const schema = zodBridge(
z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Invalid email'),
})
);
function ContactForm() {
const form = useUniversalForm({
schema,
adapter: rhfAdapter(),
defaultValues: { name: '', email: '' },
});
return (
<form onSubmit={form.handleSubmit(data => console.log(data))}>
<Field form={form} name="name" placeholder="Name" />
<Field form={form} name="email" type="email" placeholder="Email" />
<button type="submit" disabled={form.isSubmitting}>
Submit
</button>
</form>
);
}| Package | Description | Version | Size |
|---|---|---|---|
@samithahansaka/formless |
All-in-one package (recommended) | ||
@samithahansaka/formless-core |
Core types, utilities, and interfaces | ||
@samithahansaka/formless-react |
React hooks and components | ||
@samithahansaka/formless-react-hook-form |
React Hook Form adapter | ||
@samithahansaka/formless-formik |
Formik adapter | ||
@samithahansaka/formless-tanstack-form |
TanStack Form adapter | ||
@samithahansaka/formless-zod |
Zod schema bridge |
Main hook for creating a form instance.
const form = useUniversalForm({
schema: SchemaBridge, // Required: Zod bridge or custom schema
adapter: EngineAdapter, // Required: Form library adapter
defaultValues?: Partial<T>, // Optional: Initial form values
mode?: ValidationMode, // Optional: 'onSubmit' | 'onBlur' | 'onChange' | 'onTouched' | 'all'
});Returns:
| Property | Type | Description |
|---|---|---|
values |
T |
Current form values |
errors |
FormErrors<T> |
Current validation errors |
isSubmitting |
boolean |
Submission in progress |
isValid |
boolean |
Form passes validation |
isDirty |
boolean |
Form has been modified |
getValue(path) |
(path: string) => unknown |
Get value at path |
setValue(path, value) |
(path: string, value: unknown) => void |
Set value at path |
getError(path) |
(path: string) => FieldError |
Get error at path |
setError(path, error) |
(path: string, error: string) => void |
Set error at path |
clearErrors(paths?) |
(paths?: string[]) => void |
Clear errors |
register(path) |
(path: string) => FieldRegisterProps |
Register field |
handleSubmit(onValid, onInvalid?) |
Function |
Form submit handler |
reset(values?) |
(values?: Partial<T>) => void |
Reset form |
trigger(paths?) |
(paths?: string[]) => Promise<boolean> |
Trigger validation |
getFieldArray(path) |
(path: string) => FieldArrayMethods |
Get field array helpers |
<Field
form={form} // Required: Form instance
name="fieldPath" // Required: Field path
type="text" // Optional: Input type
placeholder="..." // Optional: Placeholder text
className="..." // Optional: CSS class
/>Hook for custom field implementations.
const { value, error, isTouched, isDirty, onChange, onBlur } = useField(
form,
'name'
);Hook for dynamic field arrays.
const { fields, append, prepend, remove, swap, move, update, replace } =
useFieldArray(form, 'items');From React Hook Form
// Before
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
const { register, handleSubmit } = useForm({
resolver: zodResolver(schema),
});
// After
import { useUniversalForm } from '@samithahansaka/formless-react';
import { rhfAdapter } from '@samithahansaka/formless-react-hook-form';
import { zodBridge } from '@samithahansaka/formless-zod';
const form = useUniversalForm({
schema: zodBridge(schema),
adapter: rhfAdapter(),
});From Formik
// Before
import { useFormik } from 'formik';
import { toFormikValidationSchema } from 'zod-formik-adapter';
const formik = useFormik({
initialValues,
validationSchema: toFormikValidationSchema(schema),
});
// After
import { useUniversalForm } from '@samithahansaka/formless-react';
import { formikAdapter } from '@samithahansaka/formless-formik';
import { zodBridge } from '@samithahansaka/formless-zod';
const form = useUniversalForm({
schema: zodBridge(schema),
adapter: formikAdapter(),
defaultValues: initialValues,
});| Browser | Version |
|---|---|
| Chrome | 61+ |
| Firefox | 60+ |
| Safari | 12+ |
| Edge | 79+ |
# Install dependencies
npm install
# Build all packages
npm run build
# Run tests
npm test
# Type check
npm run typecheck
# Lint
npm run lintformless/
├── packages/
│ ├── core/ # Core types and utilities
│ ├── react/ # React hooks and components
│ ├── react-hook-form/ # React Hook Form adapter
│ ├── formik/ # Formik adapter
│ ├── tanstack-form/ # TanStack Form adapter
│ ├── zod/ # Zod schema bridge
│ └── zzz-formless/ # All-in-one meta-package
└── examples/
└── basic/ # Example application
Contributions are welcome! Please read our Contributing Guide before submitting a PR.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT © Samitha Hansaka
Built with ❤️ for the React community