@opentf/web-form
A high-performance form engine built on OTF Web's signals. Fields bind directly to state, updates are surgical (no re-render), and deeply nested values are addressed by path string.
A full multi-step booking flow on one shared form — tap through it and watch the live state on the right:
Installation
Install the package from npm with your preferred package manager:
bun add @opentf/web-form
createForm
createForm returns a stable form object holding reactive values, errors, and status flags. Bind inputs with register and submit with handleSubmit.
import { createForm } from "@opentf/web-form"; export function LoginForm() { const form = createForm({ initialValues: { email: "", password: "" }, }); const onSubmit = (values) => console.log(values); return ( <form onsubmit={form.handleSubmit(onSubmit)}> <input {...form.register("email")} type="email" /> <input {...form.register("password")} type="password" /> <button type="submit">Sign in</button> </form> ); }
The same form, running live:
register(path) spreads everything a field needs — name, value, checked, error, isTouched, oninput, and onblur — so a controlled input is one line.
register follows the standard value / oninput contract, so it binds native inputs and custom components alike — including checkboxes (it also returns checked).
Reactive state
Read form.values, form.errors, and form.touched directly in your template — they are reactive proxies, so only the nodes that read a changed path update.
<p>Hello, {form.values.email || "stranger"}</p> {form.errors.email ? <span class="error">{form.errors.email}</span> : null}
Status flags
The form exposes derived flags for submit buttons and UI state:
| Flag | Meaning |
|---|---|
isValid | No errors are currently set. |
isChanged | Values differ from initialValues. |
isTouched | At least one field has been blurred. |
isSubmitting | An async submit handler is in flight. |
isValidating | An async validator is running. |
isSubmitted | A submit has completed successfully. |
submitCount | Number of submit attempts. |
<button type="submit" disabled={form.isSubmitting || !form.isValid}> {form.isSubmitting ? "Saving…" : "Save"} </button>
Next: Validation · Nested State · API Reference.