How it works
This page is the single source for the core rules of Form, Please. Other pages link to these sections.
The flow
A form moves through these parts, from the schema to the submit callback:
Standard Schema validation rules and transforms
│
▼
kit.defineForm(schema, (ui) => [...]) definition: generated UI
│
▼
kit.useForm({ defaultValues, onSubmit }) form binding
│
▼
React Hook Form editable schema input and form state
│ validation run
▼
Standard Schema resolver issues, or transformed output
│ valid submit
▼
onSubmit({ value, input, form, submitter })The diagram does not show two facts. kit.useForm creates one form binding,
and the binding creates one React Hook Form (RHF) instance. The schema is the
only resolver of that instance.
Managed updates run the definition rules
A generated control does not write to RHF directly. It sends a managed update through the rules of the definition:
generated control, generated array, or form.update
│ proposed values
▼
beforeUpdate(draft) change the draft, or return false
│
▼
middleware ordered rules, in list order
│ patches
▼
React Hook Form one commit; validation can finish later
│
▼
afterUpdate(transaction) code after the commitA dependent value, such as an order total, commits together with its source
values. React does not render the old total first. A cancelled change does not
reach RHF, and afterUpdate does not run for it.
defaultValues, resets, and direct form.api calls do not run the rules. See
Value middleware for examples and the complete list of changes.
You keep the React Hook Form API
form.api is the complete RHF API of the form binding. Use its methods, such
as setValue, trigger, and setError, in application code.
kit.Form and kit.AutoForm supply form.api through RHF FormProvider.
Inside them, register, Controller, useController, useWatch,
useFormState, useFieldArray, and useFormContext share the state of the
generated fields. The path of an application-owned field must exist in the
schema, but the field does not need a node in the definition. The schema
validates its value, and onSubmit receives its output.
An application-owned field supplies its own label, description, issue output,
and focus behavior. Direct form.api writes do not run the definition rules.
Use form.update when a change must run them. See Compose generated and
custom UI for an example.
required is UI-only
The required property changes the control UI and its accessibility state. It
does not add a validation rule. The Standard Schema must reject a missing or
invalid value.
The same rule applies to visible, disabled, and readOnly. These
properties control the generated UI. They do not change the schema.
kit.Form sets noValidate on the native form element. Browser constraints,
such as required and type="email", do not replace schema validation. Put
field rules and cross-field rules in the schema.
Hidden fields keep their values
A hidden generated field does not render, but its value stays in RHF state. The Standard Schema still receives that value during validation and submission.
Match the schema condition to the visibility condition. If you do not, an
invalid hidden value can block submission. A hidden field has no control to
focus, so kit.AutoForm shows its issue in the error summary. Read Focus the
first issue.
To remove an inactive value from the submitted output, use a schema transform.
To clear the editable value when an edit hides the field, use
whenHidden. See Conditional
fields for an
example.
The kit grid limits the layout
The definition sets the layout. columns on a section sets the number of
columns for its children. span on a field, section, or array sets the width
of that node in its parent grid. span: "full" gives the node the full row.
The kit owns the grid scale. The default grid is [1, 2, 3, 4]. The type
checker and kit.defineForm reject grid values that are not in the kit grid,
and kit.defineForm rejects a span that is larger than the parent columns.
The optional form-please/layout.css stylesheet implements the default grid.
The grid follows the width of each section, not the viewport width. A section
that is narrower than 40rem shows one column. See Configure columns and
spans.
The schema parses once on submit
Form, Please gives RHF a small Standard Schema resolver. Each validation run
parses the schema input once. On a valid submit, the same parse returns the
transformed FormOutput<Schema>. Form, Please does not parse the input a second
time to get the output, and it keeps no validation cache.
The editable FormInput<Schema> stays in RHF state. Only a successful submit
callback receives the transformed output.
RHF can validate the schema again after later user actions. Keep schema validation deterministic and free of side effects.
onSubmit receives four values
onSubmit receives one object with four values, in this order:
valueis the transformedFormOutput<Schema>.inputis the editableFormInput<Schema>, captured before validation.formis the current form binding.submitteris{ name, value }for the native submit control, ornull.
Use value for an API request. Use input when an operation needs the schema
input, for example to reset the form after a save. A string input can produce a number output.
The callback can return a promise. form.api.formState.isSubmitting stays true
until the promise settles. kit.Submit is disabled while the form validates or
submits.
The submitter snapshot does not keep a DOM element. Read Product
workflows for implicit submits and named
submit actions. A direct form.api.handleSubmit call is ordinary RHF behavior
and does not call the Form, Please onSubmit wrapper.
Server issues are not schema issues
The server must still validate each submission. A request failure is not a Standard Schema issue, and it does not appear in the form error summary. Keep request failures in application state and render them near the form.
If the server returns field issues, validate the response first. Then map each
known server path to a schema input path and apply it with form.api.setError.
Do not convert network, authorization, or service failures into field errors.
See Apply server field errors and
Map server issues
for examples.
Ownership
Each part of a form has one owner:
| Part | Owner |
|---|---|
| Validation rules and transformed output | The Standard Schema |
| Schema input, form state, and validation runs | React Hook Form, through the schema resolver |
| Generated UI, its layout, and its managed-update policy | The definition |
| Rendered design system and grid scale | The controls, slots, and grid of the kit |
| Requests, caches, persistence storage, navigation, permissions, and workflow state | The application |
Form, Please resolves definitions and renders controls. It does not keep a
second form store. Use one binding for one form, and use form.api for the
RHF API.