Typed React 19 form hooks for nested objects, one-level field arrays, file metadata, validation timing, and accessible native field registration.
Published on npm as @muradyanvano/use-form. The API is currently pre-1.0; see CHANGELOG.md for released versions.
| Resource | URL |
|---|---|
| Storybook | muradyanvano1995.github.io/use-form |
| npm | @muradyanvano/use-form |
| GitHub | muradyanvano1995/use-form |
| Issues | GitHub Issues |
| Changelog | CHANGELOG.md |
Explore interactive examples, validation behavior, field arrays, controlled components, asynchronous flows and DevTools in the Storybook documentation.
- Strongly typed values, nested paths (
address.city), and one-level field arrays (items.0.name) - Native
register()and headlessuseControllerfor custom controls - Built-in rules, custom rules, form-level
validate, Standard Schema resolvers, async debounce, i18n catalogs - Structured errors (
FieldError+ string view), accessible ids, focus-on-error - File fields store
Filereferences only (no content reads) - Non-reactive getters and atomic
form.batch() - Development DevTools on a separate entry (not shipped in core bundles)
Stable:
npm install @muradyanvano/use-formPrerelease (beta channel):
npm install @muradyanvano/use-form@betaPeers:
react:^19.0.0(required)react-dom: optional for core-only apps; required when importing@muradyanvano/use-form/devtools
Contributors can also develop from this repository (npm ci) or install a locally packed tarball (see docs/releasing.md).
Supported tooling: Node ^20.19.0 || >=22.12.0, npm >=10.8.0.
'use client'
import { rules, useForm, ValidationMode } from '@muradyanvano/use-form'
type LoginValues = { email: string; password: string }
export function LoginForm() {
const form = useForm<LoginValues>({
defaultValues: { email: '', password: '' },
mode: ValidationMode.OnSubmit,
rules: {
email: [rules.required(), rules.email()],
password: [rules.required(), rules.minLength(8)],
},
onSubmit: (values) => {
void values
},
})
return (
<form onSubmit={form.handleSubmit} noValidate>
<label htmlFor={form.getFieldId('email')}>Email</label>
<input {...form.register('email')} id={form.getFieldId('email')} type="email" />
{form.errors.email ? <p id={form.getErrorId('email')}>{form.errors.email}</p> : null}
<label htmlFor={form.getFieldId('password')}>Password</label>
<input {...form.register('password')} id={form.getFieldId('password')} type="password" />
{form.errors.password ? <p id={form.getErrorId('password')}>{form.errors.password}</p> : null}
<button type="submit">Sign in</button>
</form>
)
}Form hooks are client components. In React Server Component apps, import them from a file marked 'use client'.
import { rules, useForm, ValidationMode } from '@muradyanvano/use-form'
const form = useForm({
defaultValues: { age: '' },
mode: ValidationMode.OnBlur,
rules: {
age: [rules.required(), rules.min(18)],
},
})See docs/validation.md and docs/async-validation.md.
import { useForm, type FieldPath } from '@muradyanvano/use-form'
type Profile = {
email: string
address: { city: string }
}
const form = useForm<Profile>({
defaultValues: { email: '', address: { city: '' } },
})
const path: FieldPath<Profile> = 'address.city'
form.setValue(path, 'Yerevan')Path inference stops at depth 5. Invalid paths fail at compile time.
import { useController, useForm } from '@muradyanvano/use-form'
function RatingField({ control }: { control: ReturnType<typeof useForm>['control'] }) {
const { field, fieldState } = useController({
control,
name: 'rating',
defaultValue: 0,
})
return (
<>
<input
type="range"
min={0}
max={5}
value={field.value}
onChange={(event) => {
field.onChange(Number(event.target.value))
}}
/>
{fieldState.error ? <p>{fieldState.error}</p> : null}
</>
)
}See docs/controlled-components.md.
import { useFieldArray, useForm } from '@muradyanvano/use-form'
type FormValues = { items: Array<{ name: string }> }
const form = useForm<FormValues>({ defaultValues: { items: [] } })
const items = useFieldArray({ control: form.control, name: 'items' })
items.append({ name: '' })One index level only. See docs/field-arrays.md.
- Debounced remote checks: docs/async-validation.md
- Async
loadDefaultValues: docs/async-default-values.md
import { useForm } from '@muradyanvano/use-form'
import { standardSchemaResolver } from '@muradyanvano/use-form/resolvers/standard-schema'
const form = useForm({
defaultValues: { email: '' },
resolver: standardSchemaResolver(yourStandardSchema),
})The adapter has no React import and may run on the server. It is not exported from the core entry. See docs/schema-resolvers.md.
import { FormProvider, useForm } from '@muradyanvano/use-form'
import { FormDevTools } from '@muradyanvano/use-form/devtools'
const form = useForm({ defaultValues: { email: '' } })
return (
<FormProvider control={form.control}>
{/* fields */}
{import.meta.env.DEV ? <FormDevTools control={form.control} /> : null}
</FormProvider>
)DevTools requires react-dom (portals). Do not ship it as production UI. See docs/devtools.md.
| Import | Purpose |
|---|---|
@muradyanvano/use-form |
Core hooks, rules, types ('use client') |
@muradyanvano/use-form/devtools |
FormDevTools inspector ('use client', needs react-dom) |
@muradyanvano/use-form/resolvers/standard-schema |
standardSchemaResolver (no React) |
@muradyanvano/use-form/package.json |
Package metadata |
Private paths (for example @muradyanvano/use-form/hooks/...) are not supported.
Full inventory: docs/public-api.md.
Tested in this repository with React 19.2. React 18, React Native, and CommonJS require() are not supported or tested.
Core hooks are client-only. The library uses a stable server snapshot for selector reads during SSR smoke tests, but hydrated form UX is not a supported product surface yet. Do not render interactive forms on the server.
The Standard Schema resolver entry may be imported on the server because it does not import React.
Modern evergreen browsers with native ESM. The library targets client-side React DOM applications tested via Vitest, Testing Library, and Storybook browser runs in CI.
- ESM only (no CommonJS build)
- Path expansion depth 5; one-level field arrays; no nested arrays inside items
- No first-party Zod/Yup/Valibot adapters (Standard Schema only)
- Client-side validation is UX only — repeat checks on the server
See docs/package-roadmap.md and Storybook Limitations and roadmap.
- Interactive docs: Storybook
- Guides: docs/
- Local Storybook for contributors:
npm run storybook— rules in docs/storybook.md - API reference (TypeDoc):
npm run docs:api→ gitignoredapi-docs/
See CONTRIBUTING.md and the post-change guide docs/development-workflow.md. Run npm run verify locally; CI runs npm run verify:ci on push/PR.
See SECURITY.md. Report issues via GitHub Issues until a dedicated security contact is published.
MIT © 2026 Vano Muradyan