Compose a layout
Use the schema-bound component namespace when automatic layout is not enough.
Without children, <Profile.Form /> renders the root object automatically. Add children to control grouping and layout while keeping every field path typed.
"use client";
import { Profile } from "./profile-form";
export function ProfileEditor() {
return (
<Profile.Form action={saveProfile}>
<div className="grid gap-4 md:grid-cols-2">
<Profile.Field name="name" />
<Profile.Field name="email" />
</div>
<Profile.Field name="accountType" />
<Profile.When field="accountType" equals="company">
<section className="rounded-box border p-4">
<h2>Company details</h2>
<Profile.Fields names={["company", "taxId"]} />
</section>
</Profile.When>
<Profile.Field name="bio" />
<Profile.Submit>Save profile</Profile.Submit>
</Profile.Form>
);
}The bound namespace contains:
Form— state, validation, submission, and automatic renderingField— one typed path, with optional render-time optionsFields— selected fields, or every root field whennamesis omittedWhen— typed equality checks or a full-form predicateSubmit— the active adapter's submit buttonStep— one compositional wizard stepWizard— a multi-step form using the same runtimeuseField— field state bound to this schemauseFormState— typed values and form operationsuseFormModel— the compiled read-only schema model
Read form state inside the form
Hooks must run in a descendant of the bound Form.
import { useFormState } from "@formadapter/react";
function SaveStatus() {
const { draftStatus, isSubmitting } = useFormState();
return <p>{isSubmitting ? "Saving…" : `Draft: ${draftStatus}`}</p>;
}
function NameLength() {
const name = Profile.useField("name");
return <p>{String(name.value ?? "").length}/60</p>;
}
<Profile.Form action={saveProfile}>
<Profile.Field name="name" />
<NameLength />
<SaveStatus />
<Profile.Submit />
</Profile.Form>;useFormState() also exposes values, errors, dirty/validation state, validating fields, the latest submission state, setValue, reset, draftStatus, and clearDraft.
Advanced composed controls can inspect the compiled model with Profile.useFormModel(). The schema-bound hook retains input and custom-control types; the package-level useFormModel() is schema-neutral for reusable adapter infrastructure.
Array item paths have two deliberate forms: configuration uses a template such as invites[].email, while runtime hooks use a concrete index such as Invites.useField("invites.0.email"). Render the parent array with <Invites.Field name="invites" />; a standalone field or wizard step cannot own an unindexed item template.
defaultValues is mount-time state. To load a different record into a mounted form, call reset(nextValues) from inside the form or remount it with a new React key.