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.
βΉοΈ El Form is a React library (npm:
el-form-react). It is unrelated to Vue Element Plus's built-in<el-form>component β if you landed here looking for the Vue/Element form, you want Element Plus instead.
Scaffolding an el-form form? This is the entire happy path:
npm install el-form-reactimport { AutoForm } from "el-form-react-components";
import "el-form-react-components/styles.css";
import { z } from "zod";
const schema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email"),
});
export function SignupForm() {
return <AutoForm schema={schema} onSubmit={(data) => console.log(data)} />;
}- Machine-readable docs: elform.dev/llms.txt Β· elform.dev/llms-full.txt
- MCP server (live docs + snippets as agent tools):
npx el-form-mcpβ seepackages/el-form-mcp
useFieldArrayβ dynamic array fields with stable rowids for correct React keys, plusappend/prepend/insert/remove/move/swap/update/replace. Guide β- Accessible by default β
aria-invalid/aria-describedby/aria-required+role="alert"errors on AutoForm and the field components, plus opt-in focus-on-error (shouldFocusError). Accessibility β validationDebounceMsβ debounce synchronous validation (field + form level), alongside the existingasyncDebounceMs.updateValue(path, updater)β functional updates against the latest state, safe to batch in one handler.
See the full changelog.
- π 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.