r/javascript • u/Separate_Stand_866 • 3h ago
I built Native Form: Zero-registration, HTML-first form library for React (no {...register()} boilerplate)
npmjs.comHey r/reactjs,
Like many of you, I've used React Hook Form and Formik across several production projects. While they're powerful, I always felt friction with how modern form libraries force you to treat standard HTML forms as legacy:
- Having to manually register every input with refs: {...register("email")}
- Wrapping UI components in custom controllers
- Giant bundle sizes once you add schema resolvers and adapters
I wanted to see if we could go back to standard HTML5 forms as the single source of truth without sacrificing React ergonomics or type safety.
So I built Native Form:
👉 GitHub: https://github.com/sunilsv26/native-form
👉 npm: npm i @native-form/react
What makes it different?
- Zero Input Registration: You write plain HTML inputs with standard
nameattributes (<input name="email" />). Native Form discovers them automatically from the DOM viaMutationObserver. - Uncontrolled DOM-First Performance: Keystrokes don't trigger component re-renders. Your view only re-renders when form state (validation errors, submission status) actually changes.
- Microscopic Schema Adapters: Adapters for Zod, Yup, and Valibot are under 500 bytes. The core runtime has zero third-party dependencies.
- Built-in Multi-Step Memory:
useWizardandpreserveUnmountedValues: truekeep your unmounted step values hydrated without complex state contexts. - Framework Agnostic Under the Hood: The core runtime (
@native-form/core) runs in pure Vanilla JS, Vue, or Svelte, while@native-form/reactprovides React 18 & 19 hooks (useForm,useField,useWatch,useWizard).
Quick Example:
```tsx import { useForm } from "@native-form/react"; import { zodResolver } from "@native-form/zod"; import { z } from "zod";
const schema = z.object({ email: z.string().email(), age: z.number().min(18), });
export function Form() { const { formRef, formState } = useForm({ schema: zodResolver(schema), serializationMode: "typed", // automatically coerces type="number" and range to numbers onSubmit: (values) => console.log(values), });
return ( <form ref={formRef} noValidate> <input name="email" type="email" placeholder="Email" /> {formState.errors.email && <span>{formState.errors.email}</span>}
<input name="age" type="number" placeholder="Age" />
{formState.errors.age && <span>{formState.errors.age}</span>}
<button type="submit" disabled={formState.submitting}>Submit</button>
</form>
); } ```
I recently released v0.1.2 with fixes for discrete controls, range sliders, and custom design system integration (like Material UI).
I'd love to hear your feedback on the architecture, API design, or any edge cases you'd like to see covered!