Skip to content

Universal form adapter for React. Supports RHF, Formik, TanStack Form with Zod schema validation

License

Notifications You must be signed in to change notification settings

samithahansaka/formless

CI npm version downloads bundle size coverage license TypeScript

Formless

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.


Features

  • 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

Installation

Option 1: All-in-one package (recommended)

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

Option 2: Individual packages

# 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-form

Quick Start

import { 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>
  );
}

Packages

Package Description Version Size
@samithahansaka/formless All-in-one package (recommended) npm size
@samithahansaka/formless-core Core types, utilities, and interfaces npm size
@samithahansaka/formless-react React hooks and components npm size
@samithahansaka/formless-react-hook-form React Hook Form adapter npm size
@samithahansaka/formless-formik Formik adapter npm size
@samithahansaka/formless-tanstack-form TanStack Form adapter npm size
@samithahansaka/formless-zod Zod schema bridge npm size

API Reference

useUniversalForm(config)

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 /> Component

<Field
  form={form} // Required: Form instance
  name="fieldPath" // Required: Field path
  type="text" // Optional: Input type
  placeholder="..." // Optional: Placeholder text
  className="..." // Optional: CSS class
/>

useField(form, path)

Hook for custom field implementations.

const { value, error, isTouched, isDirty, onChange, onBlur } = useField(
  form,
  'name'
);

useFieldArray(form, path)

Hook for dynamic field arrays.

const { fields, append, prepend, remove, swap, move, update, replace } =
  useFieldArray(form, 'items');

Migration Guide

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 Support

Browser Version
Chrome 61+
Firefox 60+
Safari 12+
Edge 79+

Development

# Install dependencies
npm install

# Build all packages
npm run build

# Run tests
npm test

# Type check
npm run typecheck

# Lint
npm run lint

Project Structure

formless/
├── 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

Contributing

Contributions are welcome! Please read our Contributing Guide before submitting a PR.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT © Samitha Hansaka


Built with ❤️ for the React community

About

Universal form adapter for React. Supports RHF, Formik, TanStack Form with Zod schema validation

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

Packages

No packages published

Languages