Form
Form state, validation, and submission live in @domphy/form — a 1-1 port of @tanstack/form-core with a Domphy adapter. @domphy/ui provides the presentation: native input/select/textarea with the input patches, label, and formGroup for layout.
The old UI-level
form()/field()patches andFormState/FieldStateclasses were removed in favor of@domphy/form, so form logic lives in exactly one place.
Pattern
createForm owns the state; each field handle binds one input. Read value/errors reactively and forward DOM events to the handle:
import { createForm } from "@domphy/form/domphy"
import { inputText, label, button, formGroup } from "@domphy/ui"
const form = createForm<{ email: string }>({
defaultValues: { email: "" },
onSubmit: ({ value }) => save(value),
})
const email = form.field<string>("email", {
validators: { onChange: ({ value }) => (value.includes("@") ? undefined : "Invalid email") },
})
const Field = {
div: [
{ label: "Email", $: [label()] },
{
input: null,
$: [inputText()],
value: (l) => email.value(l),
onInput: (e) => email.handleChange((e.target as HTMLInputElement).value),
onBlur: () => email.handleBlur(),
},
],
$: [formGroup()],
}
See the @domphy/form docs for validators, async validation, arrays, Standard Schema, and the full field/form API.
formGroup
formGroup({ color?, layout? }) is the one form patch that stays in @domphy/ui — it is pure layout (label + control + help text) with no state. layout is "vertical" (default) or "horizontal". Host tag: fieldset or div.
{ div: [{ label: "Name", $: [label()] }, { input: null, $: [inputText()] }], $: [formGroup()] }