TypeScript-first React form library with zero-boilerplate AutoForm and powerful useForm hook. The best React Hook Form alternative with schema-first validation (Zod, Yup, Valibot), built-in components, and enterprise-grade form management.
- 🚀 Quick Installation
- 🎯 Core Features
- ⚡ Quick Start
- 🔧 Validation Approaches
- 🎨 Component Reusability
- 🛡️ Error Handling
- 📦 Package Structure
- 🏗️ Project Setup
- 🎨 Custom Error Components
- 🔧 Advanced Configuration
# For everything (hooks + components + styling)
npm install el-form-react
# For hooks only (like React Hook Form alternative)
npm install el-form-react-hooks
# For AutoForm components only
npm install el-form-react-componentsWhy El Form? React Hook Form alternative with AutoForm, schema-agnostic validation, TypeScript-first design, and enterprise-ready form management.
- 🚀 AutoForm Component: Generate forms instantly from Zod/Yup schemas - zero boilerplate
- ⚙️ useForm Hook: React Hook Form-compatible API with enhanced TypeScript support
- 🔥 Schema-First Validation: Zod, Yup, Valibot, or custom validation functions
- 📦 Modular Architecture: Install only what you need - hooks, components, or complete package
- 🏎️ Performance Optimized: Minimal re-renders, debounced validation, efficient state updates
- 🛡️ Type-Safe Forms: Full TypeScript integration with automatic inference
- 📊 Advanced State Tracking: Dirty fields, touched state, submission status, form history
- ⚡ Real-time Validation: Configurable validation triggers (onChange/onBlur/onSubmit)
- 🔄 Form Reset & History: Complete form state management with undo/redo capabilities
- 🧩 Component Reusability: Context pattern, form passing, and hybrid approaches
- 🎨 Flexible Styling: Works with Tailwind CSS, styled-components, CSS modules, or any styling solution
- 🛠️ Built-in Components: Pre-styled form fields, error displays, and layout components
- 📝 Extensive Documentation: Complete guides, examples, and TypeScript definitions
- 🌐 Framework Agnostic Core: Use with React, Next.js, Remix, or any React framework
- 📱 Mobile Optimized: Touch-friendly inputs and responsive design patterns
- ♿ Accessibility Built-in: ARIA attributes, keyboard navigation, screen reader support
- 🧪 Testing Friendly: Simple component testing with explicit form instances
el-form-react-hooks– Core form management hooks and utilitiesel-form-react-components– AutoForm and pre-built componentsel-form-react– Combined package with both hooks and componentsel-form-core– Framework-agnostic validation engine
For form hooks and manual control:
npm install el-form-react-hooksFor auto-generated forms with styling:
npm install el-form-react-componentsFor everything in one package:
npm install el-form-reactGenerate complete forms from schemas in seconds:
import { AutoForm } from "el-form-react-components";
import { z } from "zod";
const userSchema = z.object({
firstName: z.string().min(1, "First name required"),
email: z.string().email("Invalid email address"),
age: z.number().min(18, "Must be 18 or older"),
});
function UserForm() {
return (
<AutoForm
schema={userSchema}
onSubmit={(data) => console.log("✅ Valid data:", data)}
onError={(errors) => console.log("❌ Validation errors:", errors)}
/>
);
}AutoForm Benefits:
- ✅ Zero boilerplate - Define schema, get complete form
- ✅ Automatic layout - Responsive grid/flex layouts
- ✅ Built-in styling - Professional Tailwind CSS styling
- ✅ Error handling - Automatic error display with customization
- ✅ Type safety - Full TypeScript support
Build custom forms with complete flexibility:
import { useForm } from "el-form-react-hooks";
import { z } from "zod";
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
function LoginForm() {
const { register, handleSubmit, formState } = useForm({
validators: { onChange: schema },
defaultValues: { email: "", password: "" },
});
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register("email")} placeholder="Email" />
{formState.errors.email && (
<span className="error">{formState.errors.email}</span>
)}
<input {...register("password")} type="password" placeholder="Password" />
{formState.errors.password && (
<span className="error">{formState.errors.password}</span>
)}
<button type="submit" disabled={formState.isSubmitting}>
{formState.isSubmitting ? "Signing in..." : "Sign In"}
</button>
</form>
);
}useForm Benefits:
- ✅ Complete design control - Build exactly what you need
- ✅ React Hook Form API - Familiar
register(),handleSubmit()patterns - ✅ Advanced field logic - Complex validation and interaction patterns
- ✅ Performance optimized - Minimal re-renders and efficient updates## 🔧 Validation Approaches
El Form is validation-agnostic and supports multiple approaches:
import { z } from "zod";
import { useForm } from "el-form-react-hooks";
const schema = z.object({
email: z.string().email("Invalid email"),
age: z.number().min(18, "Must be 18+"),
});
const form = useForm({
validators: { onChange: schema },
});const customValidator = (values) => {
const errors = {};
if (!values.email?.includes("@")) {
errors.email = "Invalid email";
}
return { errors, isValid: Object.keys(errors).length === 0 };
};
const form = useForm({
validators: { onSubmit: customValidator },
});import * as yup from "yup";
import { valibot as v } from "valibot";
// Yup schema
const yupSchema = yup.object({
name: yup.string().required(),
});
// Valibot schema
const valibotSchema = v.object({
name: v.string([v.minLength(1)]),
});
// Both work with useForm!// Sometimes you just need form state management
const form = useForm({
defaultValues: { name: "", email: "" },
// No validators needed!
});El Form offers three patterns for building reusable form components:
import { FormProvider, useFormContext } from "el-form-react-hooks";
function FormField({ name, label }) {
const { register, formState } = useFormContext();
return (
<div>
<label>{label}</label>
<input {...register(name)} />
{formState.errors[name] && <span>{formState.errors[name]}</span>}
</div>
);
}
function App() {
const form = useForm({ defaultValues: { email: "" } });
return (
<FormProvider form={form}>
<form onSubmit={form.handleSubmit(console.log)}>
<FormField name="email" label="Email Address" />
</form>
</FormProvider>
);
}function FormField({ name, label, form }) {
const { register, formState } = form;
return (
<div>
<label>{label}</label>
<input {...register(name)} />
{formState.errors[name] && <span>{formState.errors[name]}</span>}
</div>
);
}
// Usage: pass form explicitly
<FormField name="email" label="Email" form={form} />;function FormField({ name, label, form }) {
// Use passed form or fall back to context
const contextForm = useFormContext();
const activeForm = form || contextForm;
// Works with both patterns!
}El Form provides flexible error management that works with any validation approach:
import { AutoForm } from "el-form-react-components";
import { z } from "zod";
const userSchema = z.object({
email: z.string().email("Please enter a valid email address"),
password: z.string().min(8, "Password must be at least 8 characters"),
});
<AutoForm
schema={userSchema}
onSubmit={(data) => console.log("Success:", data)}
onError={(errors) => console.log("Validation failed:", errors)}
/>;const { setError, clearErrors, formState } = useForm();
// Set field-specific errors
setError("email", "This email is already taken");
// Set general errors
setError("general", "Something went wrong. Please try again.");
// Clear specific or all errors
clearErrors("email"); // Clear one field
clearErrors(); // Clear all fields
// API error handling
const handleSubmit = async (data) => {
try {
await submitForm(data);
} catch (error) {
if (error.fieldErrors) {
Object.entries(error.fieldErrors).forEach(([field, message]) => {
setError(field, message);
});
} else {
setError("general", "Submission failed. Please try again.");
}
}
};const CustomErrorComponent: React.FC<AutoFormErrorProps> = ({
errors,
touched,
}) => {
const errorEntries = Object.entries(errors).filter(
([field]) => touched[field]
);
if (errorEntries.length === 0) return null;
return (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
<h3 className="text-red-800 font-semibold mb-2">
Please fix these issues:
</h3>
<ul className="space-y-1">
{errorEntries.map(([field, error]) => (
<li key={field} className="text-red-700">
<strong className="capitalize">{field}:</strong> {error}
</li>
))}
</ul>
</div>
);
};
<AutoForm
schema={schema}
customErrorComponent={CustomErrorComponent}
onSubmit={handleSubmit}
/>;const { register, watch, setError, clearErrors } = useForm();
const email = watch("email");
// Debounced server validation
useEffect(() => {
if (!email || !z.string().email().safeParse(email).success) return;
const timeoutId = setTimeout(async () => {
try {
const response = await fetch(`/api/validate-email?email=${email}`);
const data = await response.json();
if (data.taken) {
setError("email", "This email is already registered");
} else {
clearErrors("email");
}
} catch (error) {
console.warn("Email validation failed:", error);
}
}, 500);
return () => clearTimeout(timeoutId);
}, [email, setError, clearErrors]);Run pnpm dev and test these features in the live demo:
- Schema-agnostic validation works with Zod, Yup, Valibot, custom functions, or no validation
- AutoForm component creates forms declaratively from schemas
- useForm hook provides manual form control with state management
- Form submission handles valid data and prevents invalid submissions
- Form reset clears all fields and errors
- Component reusability supports Context, Form Passing, and Hybrid patterns
- Text inputs render with proper validation
- Email/password fields have appropriate input types
- Number inputs handle numeric validation
- Textarea components support multi-line text
- Select dropdowns work with option arrays
- Array fields support dynamic form sections
- Automatic error display shows validation failures from any schema library
- Custom error components can replace default styling
- Real-time validation updates errors on input change
- API error integration handles server-side validation
- General error handling manages form-level errors with setError("general", message)
- Form state management tracks values, errors, touched fields
- TypeScript integration provides proper type inference for any validation library
- Performance optimization minimizes unnecessary re-renders
- Conditional rendering shows/hides fields based on form state
- Custom field components integrate seamlessly
- AutoFormDemo - Shows declarative API
- UseFormDemo - Shows manual hook usage with isDirty
- RenderPropDemo - Shows hybrid approach
- ErrorComponentDemo - Shows 6 different error styles
- ApiComparison - Shows side-by-side feature comparison
# Install dependencies
pnpm install
# Start development server
pnpm dev
# Build for production
pnpm buildpackages/
├── el-form-core/ # Core validation utilities
│ ├── src/
│ │ ├── index.ts # Main exports
│ │ ├── validation.ts # Schema-agnostic validation
│ │ ├── utils.ts # Utility functions
│ │ └── validators/ # Built-in validators
│ └── package.json
├── el-form-react-hooks/ # React hooks package
│ ├── src/
│ │ ├── index.ts # Main exports
│ │ ├── useForm.ts # Core form hook
│ │ ├── FormContext.tsx # Context for form sharing
│ │ └── utils/ # Hook utilities
│ └── package.json
├── el-form-react-components/ # React components package
│ ├── src/
│ │ ├── index.ts # Main exports
│ │ ├── AutoForm.tsx # Declarative form component
│ │ ├── FieldComponents.tsx # Field component library
│ │ └── types.ts # Component types
│ └── package.json
├── el-form-react/ # Unified React package
│ ├── src/
│ │ ├── index.ts # Re-exports all React functionality
│ │ ├── components.ts # Component exports
│ │ ├── hooks.ts # Hook exports
│ │ └── styles.css # Default styles
│ └── package.json
docs/ # Documentation site
├── docs/ # Markdown documentation
├── src/ # Docusaurus components
└── build/ # Generated site
{
"peerDependencies": {
"react": "^18.0.0"
},
"optionalDependencies": {
"zod": "^3.0.0",
"yup": "^1.0.0",
"valibot": "^0.30.0"
},
"devDependencies": {
"tailwindcss": "^4.1.8",
"@tailwindcss/postcss": "^4.1.8",
"typescript": "^5.0.0",
"vite": "^5.0.0",
"@changesets/cli": "^2.0.0"
}
}Note: Validation libraries are optional - El Form works with any validation approach or none at all.
The library includes 6 different error component styles to demonstrate customization:
// 1. Default - Clean professional styling
// 2. Elegant - Pink gradient with rounded design
// 3. Minimal - Orange border-left, compact
// 4. Dark Mode - Dark theme with red accents
// 5. Playful - Colorful gradient with emojis
// 6. Toast - Fixed position notifications
// Create your own:
const MyErrorComponent: React.FC<AutoFormErrorProps> = ({
errors,
touched,
}) => {
const errorEntries = Object.entries(errors).filter(
([field]) => touched[field]
);
if (errorEntries.length === 0) return null;
return (
<div className="my-custom-error-styles">
{errorEntries.map(([field, error]) => (
<div key={field}>
{field}: {error}
</div>
))}
</div>
);
};El Form works with any validation library or approach:
// With Zod
import { z } from "zod";
const zodSchema = z.object({
email: z.string().email("Invalid email"),
password: z.string().min(8, "Password must be 8+ characters"),
});
// With Yup
import * as yup from "yup";
const yupSchema = yup.object({
email: yup.string().email("Invalid email").required(),
password: yup.string().min(8, "Password must be 8+ characters").required(),
});
// With Valibot
import * as v from "valibot";
const valibotSchema = v.object({
email: v.pipe(v.string(), v.email("Invalid email")),
password: v.pipe(
v.string(),
v.minLength(8, "Password must be 8+ characters")
),
});
// With custom validation
const customValidation = (data: any) => {
const errors: Record<string, string> = {};
if (!data.email?.includes("@")) {
errors.email = "Invalid email";
}
if (!data.password || data.password.length < 8) {
errors.password = "Password must be 8+ characters";
}
return Object.keys(errors).length > 0 ? errors : null;
};
// Without validation (just form state management)
<AutoForm
onSubmit={(data) => console.log("Submit:", data)}
fields={[
{ name: "email", type: "email", label: "Email" },
{ name: "password", type: "password", label: "Password" },
]}
/>;const fields = [
{
name: "email",
type: "email" as const,
label: "Email Address",
placeholder: "Enter your email",
required: true,
},
{
name: "bio",
type: "textarea" as const,
label: "Biography",
placeholder: "Tell us about yourself",
rows: 4,
},
{
name: "age",
type: "number" as const,
label: "Age",
min: 18,
max: 120,
},
];MIT License - see LICENSE file for details.
This form library is production-ready with comprehensive features including:
- ✅ Schema-agnostic validation - Works with Zod, Yup, Valibot, custom functions, or no validation
- ✅ Flexible APIs - AutoForm for rapid development, useForm for custom control
- ✅ TypeScript-first - Full type safety with any validation library
- ✅ Component reusability - Context, Form Passing, and Hybrid patterns
- ✅ Modern error handling - Automatic display with customizable components
- ✅ Performance optimized - Minimal re-renders and efficient state management
Perfect for:
- Rapid prototyping with schema-driven forms
- Custom form designs with full control
- Enterprise applications requiring type safety
- Modern React applications with any validation approach
- Teams wanting consistent form patterns across projects
- ✅ AutoForm component - Generate complete forms from schemas instantly
- ✅ Better TypeScript - Full type inference with any validation library
- ✅ Modular packages - Install only what you need
- ✅ Built-in components - Pre-styled Tailwind components included
- ✅ Schema-agnostic - Works with Zod, Yup, Valibot, or custom validators
- ✅ Modern React - Built for React 18+ with hooks-first design
- ✅ Better performance - Minimal re-renders and optimized state updates
- ✅ TypeScript-first - Designed for TypeScript from the ground up
- ✅ Active development - Regular updates and community support
- ✅ Zero configuration - Works out of the box with sensible defaults
- ✅ Simpler API - Intuitive hooks-based interface
- ✅ AutoForm magic - Declarative forms from validation schemas
- ✅ Modern ecosystem - Built for current React patterns and tools
- ✅ Comprehensive docs - Complete guides and examples
- ✅ Framework agnostic core - Can be adapted to other frameworks
Get started: npm install el-form-react and see the documentation or the in-repo docs/intro for examples and guides.