API reference
Every public method is listed here, grouped by lifecycle. Most methods are chainable and return the same FormController. Focused pages cover native control behavior, accessible validation, and formatting lifecycle without hiding those contracts outside the site.
Lifecycle
Section titled “Lifecycle”form(target, options?)— bind a controller to anHTMLFormElement. The main entry point.createFormController(target, options?)— underlying controller factory.element— the bound form (fis an alias).options— normalized controller options, including defaults and merged attributes.reset()— restore native form values and clear errors.addCleanup(cleanup)— register teardown work and receive an unregister function.destroy()— tear down listeners, observer, and caches.
Registry helpers (vanilla)
Section titled “Registry helpers (vanilla)”browser— module-level singleton that wrapsform()withnewForm/destroyForm/available. Mirrors the IIFE surface without auto-installing a global.browser.newForm— build + register a controller underbrowser.available[id].browser.destroyForm— destroy + unregister a controller by id.browser.available— registry of active controllers keyed by id.
Submission
Section titled “Submission”onSubmit(callback, preventDefault?)— register a submit handler.autoSubmit(options?)— enable submit-on-change with optional debounce.disableAutoSubmit()— turn auto-submit off and cancel pending debounce.
Field observation
Section titled “Field observation”watch(field, callback)— chainable field observation.observe(field, callback)— likewatch, but returns an unsubscribe function.unwatch(field?, callback?)— remove watched callbacks.subscribe(listener)— react to controller notification points.
Field values
Section titled “Field values”setValue(name, value)— write a value into a field.getValue(name)— read the current value of a field.getField(name)— read the underlying DOM element(s).prefill(fieldName?)— populate the form fromwindow.location.search.format(config)— apply@samline/formatterto a field.formatAll(config)— alias offormat()forfield: string[]use cases.
Validation
Section titled “Validation”validate(fields?)— run validation, return the result.revalidate(fields?)— alias ofvalidatewith explicit intent.setErrors(fields)— push manual errors.clearErrors(fields?)— clear manual errors.
State and data
Section titled “State and data”getData()— return plain object +FormDatafor the form.getState()— return a snapshot of values, errors, and metadata.append(options)— inject a DOM node into the form.
Pure helpers
Section titled “Pure helpers”These do not require a controller. They accept plain values or a raw HTMLFormElement and return results — safe to tree-shake into any bundle.
parseFormData(formElement, submitter?)— same serializer used internally, no controller needed.validateValues(values, schema)— run a schema against a values map.validateFieldValue(field, value, rules, values)— run a rule set against a single value.
Per-method reference
Section titled “Per-method reference”Lifecycle
Section titled “Lifecycle”form(target, options?)
Section titled “form(target, options?)”Creates a new controller bound to a form. This is the main entry point of @samline/forms.
function form( target: FormTarget, options?: FormControllerOptions): FormControllertarget— string id,HTMLFormElement, ref-like{ current }object, ornull/undefined.options— controller configuration. See Configuration.
On creation the controller wires delegated input and submit listeners, including support for controls associated through form="id", starts a MutationObserver on the form subtree, optionally enables autoSubmit, and synchronizes initial css-filled state. Initial visual synchronization is independent of autoValidate; only the initial validation pass is conditional on that option.
An unresolved id, non-form element, null, or empty ref still returns an inert controller. Its element is null, reads return empty/missing values, and chainable writes are no-ops. String ids and ref-like values are resolved only once; create a new controller after the element mounts. Avoid binding multiple controllers to one form because each installs its own listeners.
createFormController(target, options?)
Section titled “createFormController(target, options?)”The underlying factory used by form(). It accepts the same arguments and returns the same FormController; use it when the explicit factory name reads better in framework integrations.
element
Section titled “element”Read-only getter for the bound HTMLFormElement, or null if the binding target was unresolved at construction time. f is an alias kept short for fluent setup.
readonly element: HTMLFormElement | nullreadonly f: HTMLFormElement | nulloptions
Section titled “options”Read-only normalized FormControllerOptions. The controller creates a merged options object, and attributes always contains both resolved attribute names.
readonly options: FormControllerOptionsreset()
Section titled “reset()”Restores native and formatted default values, clears manual and validation errors plus aria-invalid, then notifies subscribers. It dispatches no input events, does not invoke field watchers, does not disable auto-submit, and does not reset submitCount. When the controller is already validated, filled attributes are recalculated immediately from default values.
addCleanup(cleanup)
Section titled “addCleanup(cleanup)”Registers controller-owned teardown work and returns an idempotent unregister function. Unregistering removes the callback without invoking it. Remaining callbacks run once in reverse registration order during destroy(); exceptions are reported and do not stop later cleanup. Registering after destruction invokes the cleanup immediately.
addCleanup(cleanup: FormCleanup): () => void
const unregister = controller.addCleanup(() => adapter.destroy())unregister() // optional: remove without runningdestroy()
Section titled “destroy()”Removes all controller listeners, disconnects the observer, cancels pending auto-submit, runs registered cleanups in reverse order, drops callbacks, clears stored errors and submit tracking, and cleans up formatter ownership. Formatted visible names are restored and only controller-created raw mirrors are removed. Existing visual attributes are not stripped. Public methods remain callable, including direct reads and writes, but controller listeners no longer react to resulting events. Calling destroy() more than once is safe.
Submission
Section titled “Submission”onSubmit(callback, preventDefault?)
Section titled “onSubmit(callback, preventDefault?)”Registers a handler that runs when the form is submitted and validation passes. Multiple handlers can be registered; each runs in registration order.
onSubmit( callback: FormSubmitHandler, preventDefault?: boolean // default: true): FormControllerThe submit pipeline clears manual errors when configured, validates, synchronizes aria-invalid, increments submitCount, and invokes every handler in registration order when valid. Invalid submissions are always intercepted and focus moves to the first focusable invalid field. If any valid-submit handler uses the default preventDefault: true, the event is prevented for all handlers. Handlers may return Promise<void>; getState().isSubmitting remains true while async handlers from any concurrent submission are pending. Fulfilled and rejected promises both settle through Promise.allSettled, so rejection does not strand submitting state. Promise settlement does not delay native navigation when every handler opts out of prevention. There is no per-handler unsubscribe method; destroy() clears all handlers. A successful named submit button contributes its name/value to fresh data and formData values.
autoSubmit(options?)
Section titled “autoSubmit(options?)”Enables native auto-submit. Every handled input schedules form.requestSubmit() (or the package’s submit fallback). Pass { debounce: ms } to delay. Auto-submit itself does not validate; validation occurs when the resulting submit event enters the normal pipeline. Disabling or destroying cancels pending timers, and re-enabling uses only the newly supplied debounce.
autoSubmit(options?: boolean | AutoSubmitOptions): FormControllerdisableAutoSubmit()
Section titled “disableAutoSubmit()”Turns auto-submit off and cancels any pending debounce timer. Equivalent to autoSubmit(false).
Field observation
Section titled “Field observation”watch(field, callback)
Section titled “watch(field, callback)”Chainable field observer. When a form is bound, the callback fires immediately and after matching handled input events; an inert controller stores the callback without an initial call. DOM mutations alone do not invoke field watchers. watch() returns the controller instead of an unsubscribe function.
observe(field, callback)
Section titled “observe(field, callback)”Like watch, but returns an idempotent unsubscribe function. The callback receives (value, field, form, state) after error clearing, validation, and visual synchronization for the input event, but before whole-form subscribers are notified.
unwatch(field?, callback?)
Section titled “unwatch(field?, callback?)”Removes watched callbacks. Three overloads: no args (clear all), field only (clear all for that field), or field + callback (clear one specific watcher).
subscribe(listener)
Section titled “subscribe(listener)”Registers a listener that always fires immediately and returns an unsubscribe function. Notifications occur for handled input, setValue, manual-error changes, reset, auto-submit toggles, submit attempts, and observed DOM mutations. Direct validate() / revalidate() calls update validation state but do not independently notify subscribers.
Field values
Section titled “Field values”setValue(name, value)
Section titled “setValue(name, value)”Writes a value and dispatches one bubbling input event from the first matching field, so the controller pipeline runs once. Returns the controller unchanged when the field does not exist.
Control-specific checkbox, radio, file, multi-select, repeated-name, and [] behavior is specified in the form-control matrix.
getValue(name)
Section titled “getValue(name)”Returns the normalized value of a field: string, string[], File[], or undefined.
The exact scalar/array behavior differs for radio groups, checkbox groups, multiple selects, files, repeated bare names, and [] names. See the reading values matrix.
getField(name)
Section titled “getField(name)”Returns the underlying DOM field(s) for a given name: a single FormFieldElement, an array (for repeated names like radio/checkbox groups), or null.
prefill(fieldName?)
Section titled “prefill(fieldName?)”Populates the form (or one field) from window.location.search. Values are strings and each query entry delegates to setValue, so normal event effects apply. Repeated query keys are not aggregated; they are written in URL order and later scalar writes can replace earlier state. In non-DOM environments it is a no-op.
format(config)
Section titled “format(config)”Apply an @samline/formatter pipeline to one or more input/textarea fields. The method synchronously creates the canonical/display pair, returns the controller immediately, then asynchronously loads and binds the peer. A custom displayField works only for one field; arrays derive <field>_displayed separately. Selects and hidden visible targets are unsupported. Read the complete formatting lifecycle and mirror contract.
Initial server-rendered values are normalized after the peer loads with automatic raw/display interpretation unless interpretInputAs is explicitly configured. See server-prefilled values.
formatAll(config)
Section titled “formatAll(config)”Alias of format() for readability when field is string[].
Validation
Section titled “Validation”validate(fields?)
Section titled “validate(fields?)”Runs configured rules and returns merged validation plus manual errors. With a field list, existing validation errors for other fields remain. Names without rules have their prior validation errors removed. The method marks the form as validated and synchronizes visual attributes, but does not independently notify subscribers. See validation semantics.
revalidate(fields?)
Section titled “revalidate(fields?)”Alias of validate kept separate for readability at call sites that want to express “re-run validation now”.
setErrors(fields)
Section titled “setErrors(fields)”Pushes manual errors into the form. Two overloads: array form (string[], default message “Invalid value.”) and map form (FormErrors, custom messages per field).
clearErrors(fields?)
Section titled “clearErrors(fields?)”Removes manual errors. Validation errors from rules are not touched. Visual attributes are re-synced for the affected fields.
State and data
Section titled “State and data”getData()
Section titled “getData()”Returns fresh { data, formData } values. Native successful-control rules apply, repeated names become arrays in data, and empty file placeholders are removed. Files remain File objects. See serialization differences.
getState()
Section titled “getState()”Returns a fresh snapshot: { values, errors, filledFields, isValid, isValidated, autoSubmit, isSubmitting, submitCount }. It is a pure read and does not validate or notify. isValid only means the currently stored merged error map is empty, so an unvalidated form may appear valid. isSubmitting tracks pending async submit-handler groups, including overlapping valid submissions.
append(options)
Section titled “append(options)”Creates a node and assigns content to innerHTML; sanitize untrusted content. When class is present, the first existing descendant matching its first class token is removed. The method clears the field cache but does not validate, notify subscribers, or schedule auto-submit. Appended nodes survive reset() and destroy(). Returns the node or null for an inert controller.
const banner = controller.append({ tag: 'p', content: '', class: 'status' })if (banner) banner.textContent = untrustedMessage // safe text-only alternativePure helpers
Section titled “Pure helpers”parseFormData(formElement, submitter?)
Section titled “parseFormData(formElement, submitter?)”function parseFormData( formElement: HTMLFormElement, submitter?: HTMLElement | null): SerializedFormResultSame serializer the controller uses internally. Pass the successful submit button to include its name/value. Repeated names become arrays, empty file placeholders are filtered, and reserved names such as constructor and __proto__ remain ordinary own properties. This helper has no validation or controller side effects.
validateValues(values, schema)
Section titled “validateValues(values, schema)”function validateValues( values: FormValues, schema: ValidationSchema): ValidationResultRuns every exact schema key against a values map. Returns { isValid, errors } without DOM or controller side effects. Wildcard schema keys are not expanded.
validateFieldValue(field, value, rules, values)
Section titled “validateFieldValue(field, value, rules, values)”function validateFieldValue( field: string, value: FormFieldValue, rules: FieldValidationRules, values: FormValues): string[]Runs every built-in and custom rule against one value and returns all messages. Pattern and numeric/range checks skip empty values; bounds are inclusive; sameAs compares two non-empty values; custom validators still run. each validates array members and flattens their messages into the returned string[], but this DOM-free helper cannot provide context.element. This pure helper does not react to dependsOn. See the rule behavior table.
Registry helpers (vanilla)
Section titled “Registry helpers (vanilla)”The vanilla entrypoint exports a browser singleton with the same shape as the IIFE bundle’s window.Forms — but as a plain ESM value with no globalThis side-effect. Use it from a bundler when you want the registry ergonomics without the IIFE.
browser
Section titled “browser”const browser: FormsApiModule-level singleton. Exposes form, newForm, destroyForm, and available. Spread it into your own globals ({ ...browser, regex }) or call its methods directly. The registry is shared across spreads, so window.Form.available and browser.available always point to the same object. See the Browser registry helpers section in the getting-started guide and FormsApi for the exact shape.
browser.newForm
Section titled “browser.newForm”Build a controller via browser.form(id, options) and store it in browser.available[id]. An existing controller under the id is destroyed before replacement. A missing id logs Form ID is required and returns undefined.
const contact = browser.newForm({ id: 'contact-form', options: { validators: { email: { required: true } } }})browser.destroyForm
Section titled “browser.destroyForm”Look up browser.available[id], call destroy(), and delete the entry. A missing id logs an error; an absent entry logs a warning.
browser.destroyForm('contact-form')browser.available
Section titled “browser.available”Shared mutable registry: { [id: string]: FormController }. Prefer newForm() and destroyForm() to manage it.
for (const controller of Object.values(browser.available)) { controller.validate()}For an equivalent surface in a no-bundler setup, see the Browser global reference.
External integration
Section titled “External integration”regex from @samline/formatter
Section titled “regex from @samline/formatter”A named dictionary of common regular expressions and their default error messages, intended to feed the pattern rule of any field validator. Full reference, examples, and edge cases live in the dedicated regex page — do not redeclare hand-rolled patterns when the peer is on disk.