Changelog
All notable changes to the el-form project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased] — Added
✨ AutoForm theming — Tailwind-free styles, themes, and classNames slots — el-form-react-components
- Tailwind-free shipped CSS.
el-form-react-components/styles.cssis now hand-authored plain CSS, tokenized with--el-form-*CSS variables and wrapped in an@layer el-form. The package needs no consumer Tailwind — themes and overrides work in any project. Standalone field components (TextField,SelectField,TextareaField) andFormSwitchare also styled by this CSS. - New
themeprop onAutoForm—<AutoForm theme="dark" />, accepting"default" | "minimal" | "dark". Setsdata-el-form-themeon the container; the theme variable blocks are bundled in the shipped CSS (no extra import). - New
classNamesslots onAutoForm— a global map to append your own classes (Tailwind utilities or custom CSS) to each element:container,form,layout,field,label,input,select,textarea,checkbox,error,submitButton,resetButton,actions,arrayItem,arrayHeader,arrayAddButton,arrayRemoveButton. Because base styles live in@layer el-form, your unlayered classes win the cascade. - CSS-variable overrides — fine-tune without a full theme by setting
--el-form-*tokens (e.g.--el-form-accent,--el-form-radius,--el-form-bg,--el-form-border,--el-form-error) on a wrapper or:root. - Behavior refinement: the existing per-field className props
(
className/inputClassName/labelClassName/errorClassName, set via thefieldsconfig) now append over the base.el-form-*class instead of replacing it — the base class is always present beneath your overrides. - All additive. See the new Styling & Themes guide.
✨ New formState fields + validation-timing options — el-form-react-hooks
formState.isValidating: boolean—truewhile any async validation is in flight (anonChange/onBlur/submit/trigger()async validator), andfalseotherwise. Reset byreset(). (Previously there was noisValidatingonformState.)formState.dirtyFields: Partial<Record<string, boolean>>— a reactive, flat, path-keyed map of dirty fields (e.g.{ "profile.name": true }); the reactive twin of the existinggetDirtyFields()method. Keys are path strings (matchinggetDirtyFields()), notkeyof T. Reset byreset().mode: "onTouched"— a new value for the existinguseForm({ mode })option. Validates a field on its first blur, then on every change once it has been touched (matches React Hook Form'sonTouched).reValidateMode?: "onChange" | "onBlur" | "onSubmit"— a new opt-inuseFormoption (defaultundefined= behavior unchanged). Once the form has been submitted, it pins post-submit re-validation to the single chosen event (RHF-style). Note:register'sonChangeeagerly clears a field's error before the gate, so withreValidateMode: "onBlur"a post-submit keystroke visibly clears the error immediately and a later blur re-adds it if still invalid.- All four additions are additive and backward-compatible — existing forms are unaffected. See the Form State concept and the useForm API.
[Unreleased] — Fixed
🐞 Bug Fix — async validators now actually run (el-form-react-hooks)
onChangeAsync/onBlurAsync/onSubmitAsync(field- and form-level) were silent no-ops. The hooks layer never dispatched an async validation event, so every async validator — includingasyncDebounceMs/*AsyncDebounceMsdebounce config and theasyncAlwaysflag — had zero effect. They now run:- Sync validators fire first (instant feedback); async validators run only if
sync passes, unless
asyncAlways: trueis set on that config. - Change/blur async is non-blocking — the async result updates the UI when it settles, with stale-result protection so superseded in-flight results are discarded.
submit()/handleSubmit/trigger()await async validation; a failing async rule blocks submission.- Form-level
validators.onSubmitAsyncruns on submit and can return{ fields: { <name>: msg } }to attach per-field errors.
- Sync validators fire first (instant feedback); async validators run only if
sync passes, unless
- Behavior change: forms that configured async validators will now surface async errors and can gate submission where they previously passed silently.
- See the updated Async Validation Guide.
[Unreleased] — Docs
📚 Interactive Playground + live theme previews
- Interactive Playground & inline sandboxes. elform.dev now has a
live Playground and editable code sandboxes embedded
throughout the docs, so you can run
AutoForm/useFormexamples directly in the browser. - Live theme previews. The new Styling & Themes guide
renders the
default/minimal/darkthemes — plus aclassNamesoverride — as live, interactive forms.
[3.15.0] - 2026-06-09
Released: el-form-react-hooks@3.15.0, el-form-react-components@4.7.2,
el-form-react@4.1.14.
✨ New formState fields — el-form-react-hooks@3.15.0
- Submit-status metadata, for React Hook Form parity.
formStategains three additive fields, all set consistently acrosshandleSubmit,submit(), andsubmitAsync(), and all reset byreset():isSubmitted: boolean—trueafter the first submit attempt.isSubmitSuccessful: boolean—truewhen the last submit passed validation and the submit handler ran without throwing.submitCount: number— number of submit attempts.
- Purely additive — existing forms are unaffected.
[3.14.0] - 2026-06-09
Released: el-form-react-hooks@3.14.0, el-form-react-components@4.7.1,
el-form-react@4.1.13.
✨ New Feature — reactive values + keepDirtyValues
useForm({ values }): a reactive external-values option for forms backed by props or server data. When thevaluesobject's content changes, the form re-syncs to it (deep-compared, so a new-object/same-content render is a no-op — no memoization required). It takes precedence overdefaultValuesfor the initial state. This is the el-form equivalent of React Hook Form'svaluesprop and Formik'senableReinitialize.keepDirtyValues: true: pair it with reactivevaluesto preserve fields the user is mid-editing (dirty) while untouched fields still sync — the equivalent of RHF'svalues+resetOptions: { keepDirtyValues: true }.- Additive — existing forms are unaffected. Notes:
valuesreplaces the whole value object (provide the full shape, not a partial patch — omitted keys are dropped);isDirtyis still measured against the originaldefaultValues, not the latest syncedvalues; and don't putFile/Blobinstances in reactivevalues(the deep-compare can't tell two files apart, so a file swap won't re-sync).
[3.13.0] - 2026-06-09
Released: el-form-react-hooks@3.13.0, el-form-react-components@4.7.0,
el-form-react@4.1.12.
⚡ TypeScript performance win — Path<T>
Path<T>no longer emits the duplicate bracket (items[0]) array-path forms, which doubled the path union at every nesting level. Type instantiations drop ~7.8× at depth 6 (≈992K → ≈127K), and el-form now type-checks faster than React Hook Form on realistic nested schemas (≤ RHF at every depth, strictly faster at depth ≥ 4).
⚠️ Breaking (types only)
- Bracket-index path notation is no longer type-checked — it becomes a
tscerror. This affectsPath<T>-typed APIs in both packages:register("items[0].name")/useField/useWatch(hooks), and thenameprop ofTextField/SelectField/TextareaField/BaseFieldProps(components). - Runtime is unaffected — bracket paths are still normalized internally and
PathValuestill resolves them. The fix is to switch the type to dot notation:items[0].name→items.0.name, and`items[${i}]`→`items.${i}`. Dot notation was already the form used throughout el-form's docs and examples.
[3.12.0] - 2026-06-09
Released: el-form-react-hooks@3.12.0, el-form-react-components@4.6.1,
el-form-react@4.1.11.
✨ New Hook — useWatch
-
useWatch: a reactive hook for subscribing to form value(s) by path, for React Hook Form parity. A reactive mirror ofform.watch()'s overloads, built on the selector store so each watcher re-renders in isolation:const all = useWatch<MyForm>(); // Partial<MyForm>
const email = useWatch<MyForm, "email">("email"); // string
const pair = useWatch<MyForm, "a" | "b">(["a", "b"]); // { a: ...; b: ... }Must be used within a
<FormProvider>. Returns values only — useuseFieldforvalue + error + touched, oruseFormSelectorfor an arbitrary derived slice. The imperativeform.watch()is unchanged. See the useForm API →useWatch.
[3.11.4] - 2026-06-08
Released: el-form-react-hooks@3.11.4, el-form-react-components@4.5.x,
el-form-react@4.1.x.
🐞 Bug Fix — FormProvider context reactivity
FormProvidercontext getter no longer lags one render behind. A component readinguseFormContext().form.formStatedirectly during render previously observed form state one render late, because the context getter returned a ref refreshed only in a post-commit effect. The ref is now updated during render, so direct reads are current within the same render pass. No public API change. (AReact.memo-wrapped child with unchanged props still won't re-render on a bare direct read — subscribe viauseField/useFormSelectorwhen a component must react to state changes.)
Maintenance — Zod 3 AutoForm fix
Released: el-form-core@2.3.3, el-form-react-components@4.7.3,
el-form-react-hooks@3.15.1, el-form-react@4.1.15.
🐞 Bug Fix — AutoForm field generation under Zod 3.x
- AutoForm rendered zero fields when used with Zod 3.x (only the Submit/Reset buttons appeared). It read a
ZodObject's shape fromgetDef(schema).shape, which in Zod 3 is a getter function (in Zod 4 the shape is already an object), so iterating it produced no keys. A newgetObjectShapehelper inel-form-coreinvokes the getter when needed and falls back to the public.shape, so AutoForm now generates fields across Zod 3 and Zod 4.
[3.11.0] - 2026-06-05
Released: el-form-react-hooks@3.11.0, el-form-react-components@4.5.0,
el-form-core@2.3.0, el-form-react@4.1.5.
✨ New Feature — el-form-react-hooks@3.11.0
useFieldArray: a new hook for dynamic array fields. Returns afieldsarray where each row carries a stableidto use as the Reactkey(fixing thekey={index}anti-pattern that breaks focus and values when rows are inserted, reordered, or removed from the middle), plusappend,prepend,insert,remove,move,swap,update, andreplace. Works inside<FormProvider>(re-renders only when its array changes) or with aformprop.nameis type-restricted to array-valued paths and item types are inferred. See the Array Fields guide.- Optional
keyName(default"id") lets items that already have a domainidfield choose a non-colliding key (e.g.keyName: "_key") so the generated row key never shadows their data. - The existing
addArrayItem/removeArrayItemhelpers are unchanged (now backed by a shared array engine). Fully backward-compatible — additive only.
- Optional
🆕 New API — el-form-react-hooks updateValue
updateValue(path, updater): apply a functional update to a field against the latest state (e.g.updateValue("items", (prev) => [...prev, item])). Avoids the stale-snapshot "lost update" pitfall when several updates run in one event handler.useFieldArrayuses this internally so multiple synchronous operations all apply correctly.
♿ Accessibility
- AutoForm and the standalone
TextField/TextareaField/SelectFieldare now accessible by default. Inputs wirearia-invalid,aria-describedby(pointing at the error element), andaria-required; field errors render withrole="alert"so screen readers announce them. No markup changes required in your code. - Focus-on-error:
useFormgainsshouldFocusError(defaulttrue). After a failed submit, focus moves to the first invalid field. To opt out:useForm({ shouldFocusError: false }). (registernow also returns aref, which is what makessetFocus/focus-on-error work.) - Fix: the standalone field components now show validation errors on the first blur. They previously could lag one render behind because they read form state through the context getter; they now subscribe via
useField.
⏱️ Validation debounce
validationDebounceMs: new config to debounce synchronous validation, at both form and field level (default0= validate every change, unchanged). Mirrors the existingasyncDebounceMs. Example:useForm({ validators: { onChange: schema, validationDebounceMs: 200 } }).
2026-06-01
🐞 Bug Fixes — el-form-react-hooks@3.10.2
handleSubmitnow blocks submission when a configuredvalidators.onChange(oronBlur) validator fails. Previously onlyvalidators.onSubmitgated submit, so a form validating on change could still submit invalid data. Forms following the quick-start (validators: { onChange: schema }) are now correctly blocked on invalid input.trigger()now writes validation errors intoformState.errors(and updatesisValid) so the UI reflects them, matching React Hook Form. Previously it returned the correct boolean but leftformStateuntouched.
🆕 New Package — el-form-mcp@0.1.0
el-form-mcp: a Model Context Protocol server that gives AI coding agents accurate El Form knowledge and code generation. Run it withnpx el-form-mcp. See the MCP Server guide.- Tools:
el_form_overview,el_form_list_topics,el_form_get_topic,el_form_search, andel_form_scaffold_form(generates AutoForm/useForm code plus a matching Zod schema from a field list). - Machine-readable docs also published at
/llms.txtand/llms-full.txt.
- Tools:
🔧 Maintenance
el-form-core@2.2.1,el-form-react-components@4.4.2,el-form-react@4.1.4: repaired the release pipeline (CSS build no longer usespnpm dlx;el-form-coretest script no longer hangs CI) and added a committed test suite foruseFormbehavior + a pre-launch example-app sweep.
[4.2.0] - 2025-08-24
✨ Changes
- FormSwitch (anchored API):
valuestuple is now optional. Provide a readonly tuple (as const) to enable compile-time duplicate detection and exhaustiveness checks. No breaking changes.
[4.1.0] - 2025-08-16
✨ New Features
-
Selector-based subscriptions to minimize re-renders
- New hooks in
el-form-react-hooks:useFormSelector(selector, equality?): subscribe to a selected slice of form stateuseField(path): subscribe to{ value, error, touched }for a field path
- Export
shallowEqualfor common array/object selector equality - SSR-safe snapshots: server snapshot equals client initial selector result
- New hooks in
-
FormSwitchoptimization (inel-form-react-components)- New props:
field?: Path<T>andselect?: (state) => string | number | boolean - Re-renders only when the discriminator changes
- Deprecated (back-compat for one minor):
onandformprops; dev-onlyconsole.warn
- New props:
🧪 Examples & Tests
- Added examples under
examples/react/testsdemonstratingfield,select, back-compat, anduseFieldre-render isolation - Added unit tests for selector subscriptions and
FormSwitchruntime behavior
📚 Docs
- Updated
Conditional Rendering (FormSwitch)guide to preferfield/select - Updated
Field Components APIfor newFormSwitchprops - Updated
useFormAPI with selector subscription guidance andshallowEqualnote - Updated
useFormAPI to reflect strictregister<Name extends Path<T>>typing (no string fallback). Invalid paths now error; array paths are supported when valid.
[4.0.0] - 2025-08-12
⚠️ Breaking
- Require
zod@^4.0.0. If you are on Zod 3, upgrade withpnpm add zod@^4.
✨ Changes
- Internal: Zod 4 introspection (no breaking surface changes to AutoForm/useForm APIs).
- Discriminated unions: more robust introspection; behavior unchanged for users.
- TypeScript:
useForm.registeris now type-safe for known paths- Literal field names and dot-paths infer exact field value type and return the appropriate props (
value,checked, orfiles). - Dynamic string paths remain supported and return a broadly-typed object to preserve backwards compatibility (no app code changes required).
- Other APIs now accept typed paths where applicable:
setValue,watch,resetField.
- Literal field names and dot-paths infer exact field value type and return the appropriate props (
[3.6.0] - 2025-08-06
✨ New Features
- Pre-compiled CSS Support: Added optional pre-compiled Tailwind CSS for zero-configuration styling
- Import
"el-form-react-components/styles.css"for instant beautiful forms - No Tailwind CSS installation required for end users
- Backwards compatible - existing Tailwind class approach continues to work
- 31KB minified CSS with semantic
.el-form-*classes - Professional gradient buttons, rounded inputs, focus states, and error styling
- Import
Previous Releases
File Upload Support (v3.3.0+)
- Native file input support with zero configuration
- File validation system with preset validators (image, document, avatar, gallery)
- Zod schema integration for file validation (z.instanceof(File))
- File management methods (addFile, removeFile, clearFiles)
- Automatic file preview generation
- Support for single and multiple file inputs
AutoForm Style Props (v3.4.0+)
- Enhanced styling capabilities for AutoForm components
- Better customization options
Cross-Package Navigation (v3.5.0+)
- Added comparison table to help users choose the right package
- Clear warnings about styling dependencies for AutoForm components
- Direct users to el-form-react-hooks for custom styling needs
- Better onboarding to reduce confusion about package selection
Migration Guide
Upgrading to Latest Version
No breaking changes in recent releases. All updates are backwards compatible.
For detailed technical changelogs, see individual package CHANGELOG.md files.