Skip to content

Latest commit

 

History

History
291 lines (227 loc) · 12.5 KB

File metadata and controls

291 lines (227 loc) · 12.5 KB

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