Skip to content
Form, Please

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 commit

A 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:

  • value is the transformed FormOutput<Schema>.
  • input is the editable FormInput<Schema>, captured before validation.
  • form is the current form binding.
  • submitter is { name, value } for the native submit control, or null.

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:

PartOwner
Validation rules and transformed outputThe Standard Schema
Schema input, form state, and validation runsReact Hook Form, through the schema resolver
Generated UI, its layout, and its managed-update policyThe definition
Rendered design system and grid scaleThe controls, slots, and grid of the kit
Requests, caches, persistence storage, navigation, permissions, and workflow stateThe 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.

Next steps