Ilha Form
@ilha/form is a tiny, typed form binding library for ilha islands. It connects a Standard Schema validator to a native <form> and gives you typed submit handling, per-field errors, dirty state, default values, and DOM-based validation — no runtime dependencies beyond your chosen schema library.
How it relates to ilha
@ilha/form is not a form component — it is a lifecycle binding. createForm() attaches to a real HTMLFormElement in your island's rendered HTML. You call form.mount() inside .effect() and return the cleanup, giving the form the same reactive lifetime as the island it lives in.
Install
Quick start
createForm() creates the binding, and form.mount() attaches the listeners and returns a cleanup function. This fits naturally inside an ilha .effect() lifecycle.
API
createForm(options)
Creates a form binding instance for a native HTMLFormElement. It does not attach listeners until mount() is called, and it uses native DOM events only.
Options
Form methods
form.mount()
Attaches listeners, applies defaultValues, resets dirty and error state, and returns a cleanup function equivalent to form.unmount(). Calling the returned cleanup function prevents further submit handling.
form.unmount()
Removes listeners and disconnects the MutationObserver used for re-applying tracked values after re-renders. It is idempotent and safe to call multiple times.
form.values()
Reads the current form values with FormData, validates them synchronously, and returns a discriminated union. It never throws validation failures, and it works even before mount().
form.errors()
Returns a copy of the per-field error map from the most recent validation run. It is empty before the first validation, and mutating the returned object does not affect internal state.
form.isDirty()
Returns true after field interaction marks the form dirty, and resets to false on a new mount(). Programmatic setValue() does not mark the form dirty.
form.submit()
Triggers the same validation and submit cycle programmatically. When mounted, it dispatches a real SubmitEvent; otherwise it validates directly and calls the same handlers.
form.setValue(name, value)
Programmatically applies a value to matching DOM fields. Supports text inputs, checkboxes, radios, selects, multi-selects, and textareas. Skips file inputs, and works even when the form is not mounted.
Validation behavior
validateOn controls when field-level validation runs automatically. The "submit" mode is more nuanced than "submit only": while the form has no active errors it stays quiet, but after a failed submit it revalidates on later change and input events until the errors are cleared.
This keeps the form quiet before the user tries to submit, then more responsive while they fix invalid fields.
Default values
defaultValues are applied on mount(), not at createForm() time. They are tracked and re-applied after DOM re-renders with a MutationObserver, while preserving user-edited values for tracked fields when possible.
defaultValues support plain inputs, checkbox groups, radios, select, select[multiple], and textarea. File inputs are skipped.
Helpers
issuesToErrors(issues)
Converts Standard Schema issues into a field-error object keyed by dot-separated paths like user.email. Each value is an array of messages, so multiple messages for the same field are preserved.
Notes
- Validation is synchronous only — async schema validation is not supported, and async validators return a failed result instead of throwing.
- The library relies on native
FormData,SubmitEvent,MutationObserver, and DOM events. form.errors()andform.values()are methods, not properties.issuesToErrorsshould be imported explicitly when used.
TypeScript
All types are inferred from the schema output. Key exported types: