Skip to content
Form, Please

TypeScript

Import the types on this page from form-please.

import type { FieldPath, FormInput, FormOutput } from "form-please"

This page covers every type exported from the root entry point. Types for native controls, default slots, and Material UI use their documented subpath entries.

For functions, components, options, and runtime behavior, read the API reference.

Choose the type you need

Start with these types in application code:

TaskType
Type form state and defaultValues.FormInput<Schema>
Type a successful submit result.FormOutput<Schema>
Accept a valid field name.FieldPath<Value>
Type shared form UI.UiNode<Input, Controls, ...>
Type a reusable form fragment.FormFragment<Schema, Controls, ...>
Type a custom control component.ControlProps<Value, OwnProps, Context, Option>
Type form-local value middleware.FormMiddleware<Input, Context>

Most calls to defineForm, useForm, and createFormKit infer the remaining types. Add an explicit type when you export reusable configuration or build a library integration.

Schema input and output

StandardSchema<Input, Output> is the common schema contract. Form, Please works with any library that implements this contract.

FormInput<Schema> is the editable value stored in React Hook Form. FormOutput<Schema> is the parsed value passed to onSubmit. These types can be different when the schema transforms data.

type ProfileInput = FormInput<typeof profileSchema>
// { name: string; age: string; contacts: { email: string }[] }
 
type ProfileOutput = FormOutput<typeof profileSchema>
// { name: string; age: number; contacts: { email: string }[] }
 
const defaultValues = {
	name: "Ada Lovelace",
	age: "36",
	contacts: [{ email: "ada@example.com" }],
} satisfies ProfileInput

Use the input type for defaultValues, field paths, controls, and resolvers. Use the output type after successful submission. Do not use the output type to describe form state.

TypePurpose
StandardSchema<Input, Output>Describes a Standard Schema validator. Output defaults to Input.
FormInput<Schema>Extracts the schema input type.
FormOutput<Schema>Extracts the schema output type.
DeepReadonly<Value>Recursively models a readonly value. It does not freeze data at runtime.

Field paths

Path types use the schema input shape and React Hook Form path syntax. Array indexes use dot segments, such as contacts.0.email.

const contactEmailPath = "contacts.0.email" satisfies FieldPath<ProfileInput>
const contactsPath = "contacts" satisfies ArrayFieldPath<ProfileInput>
 
type ContactEmail = PathValue<ProfileInput, "contacts.0.email">
// string
 
type FieldUpdate<Value, Path extends FieldPath<Value>> = {
	readonly path: Path
	readonly value: PathValue<Value, Path>
}
 
const emailUpdate = {
	path: "contacts.0.email",
	value: "grace@example.com",
} satisfies FieldUpdate<ProfileInput, "contacts.0.email">
TypeResult
FieldPath<Value>All valid string paths in Value.
PathValue<Value, Path>The value at one valid path.
ArrayFieldPath<Value>RHF field-array paths whose items are objects.

Use FieldPath when a component or helper accepts any field. Add a second path generic when its value type must stay linked to the selected path. The FieldUpdate type in the example keeps this relationship.

Definition and UI types

kit.defineForm() accepts a UI tree and returns a normalized FormDefinition. Let the call infer the type for a definition that stays in one file.

Use UiNode when the UI tree is declared separately:

type ProfileNode = UiNode<
	ProfileInput,
	typeof profileKit.controls,
	ProfileContext
>
 
const profileUi = [
	{
		kind: "field",
		path: "name",
		control: "text",
		label: "Name",
		readOnly: (_values, { context }) => !context.canEdit,
	},
	{
		kind: "array",
		path: "contacts",
		label: "Contacts",
		itemDefault: { email: "" },
		children: [
			{
				kind: "field",
				path: "email",
				control: "text",
				label: "Email",
			},
		],
	},
] satisfies readonly ProfileNode[]
 
const profileDefinition = profileKit.defineForm(profileSchema, {
	ui: profileUi,
})

The node generics carry the schema input, control registry, context, slot configuration, selectable options, and grid values through the complete tree.

TypePurpose
UiNode<Input, Controls, ...>Union of ordinary field, section, array, and render source nodes.
FieldNode<Input, Controls, ...>Field node with compatible paths, controls, props, and selectable options.
SectionNode<Input, Controls, ...>Section node with typed child nodes.
ArrayNode<Input, Controls, ...>Array node whose child paths are relative to one item.
RenderNode<Input, Context>React component node with resolved interaction state.
FormFragment<Schema, Controls, ...>Schema-owned UI fragment created by defineFragment.

FormDefinition<Schema, Controls, ...> is the normalized, readonly result of defineForm. It contains the schema, grid, and normalized nodes. Do not create this object manually. Its generic parameters preserve the kit contracts.

Resolver types

A resolvable UI property accepts a static value or a synchronous resolver. Ordinary form resolvers receive the complete schema input and { context }. Fragment-authored resolvers receive the local fragment input instead.

type Visibility = UiResolver<boolean, ProfileInput, ProfileContext>
 
const canShowProfile: Visibility = (values, { context }) =>
  context.canEdit && values.name.length > 0
TypePurpose
UiResolver<Result, Input, Context>Synchronous function that returns a derived UI value.
UiResolverValues<Input>Deeply readonly resolver values.
UiResolverDetails<Context>Object that contains deeply readonly context.
Resolvable<Value, Input, Context>Static Value or a UiResolver that returns it.
ReactUiContentA React element or string accepted by UI content properties.

Resolvable excludes a static function when Value is callable. This rule prevents Form, Please from confusing a component with a resolver.

DefaultGridValue is the default grid union: 1 | 2 | 3 | 4. A custom kit infers its grid union from createFormKit({ grid }).

Render-node types

RenderNodeComponent is a React component that receives RenderNodeProps. The props contain only disabled and readOnly. Use RenderNode<Input, Context> to type the complete node.

Control types

defineControl<Value, OwnProps, Context, Option>() returns a typed ControlDefinition. The value type controls which schema fields can use the control. The context type controls which form kits can use it.

function MoneyControl({
	value,
	setValue,
	blur,
	input,
	meta,
	props: moneyProps,
	context,
	disabled,
	readOnly,
	required,
}: ControlProps<number | undefined, MoneyProps, MoneyContext>) {
	return (
		<>
			<input
				aria-describedby={input["aria-describedby"]}
				aria-invalid={meta.invalid || undefined}
				disabled={disabled}
				id={input.id}
				lang={context.locale}
				min={moneyProps.min}
				name={input.name}
				onBlur={blur}
				onChange={(event) => {
					const nextValue = event.currentTarget.valueAsNumber
					if (Number.isNaN(nextValue)) {
						setValue(undefined)
						return
					}
					setValue(nextValue)
				}}
				readOnly={readOnly}
				ref={input.ref}
				required={required}
				type="number"
				value={value ?? ""}
			/>
			<span aria-hidden="true">{moneyProps.currencyLabel}</span>
		</>
	)
}
 
const moneyInput = {
	component: MoneyControl,
} satisfies DefineControlInput<
	number | undefined,
	MoneyProps,
	MoneyContext,
	never
>
 
const money = defineControl<number | undefined, MoneyProps, MoneyContext>(
	moneyInput,
)
 
type MoneyValue = ControlValueOf<typeof money> // number | undefined
type MoneyControlOwnProps = ControlOwnPropsOf<typeof money> // MoneyProps
type RequiredContext = ControlContextOf<typeof money> // MoneyContext
TypePurpose
ControlProps<Value, OwnProps, Context, Option>Props passed to a custom control component.
ControlDefinition<Value, OwnProps, Context, Option>Typed control returned by defineControl.
DefineControlInput<Value, OwnProps, Context, Option>Input object accepted by defineControl.
OptionValue<Value>Marks a control option value for narrowing to the selected schema input path.
FieldOptionsSource<Option, Input, Context>Static options or a tracked options function.
FieldOptionsResolver<Option, Input, Context>Function that returns options synchronously or asynchronously.
FieldOptionsResolverDetails<Input, Context>Readonly values, context, and cancellation signal.
ControlDefinitionRegistryReadonly record of named control definitions.
AnyControlDefinitionNon-specific control type used in registries.

Put OptionValue<Value> on the scalar option or on one property of a structured option. A field definition replaces the marker with the compatible schema input union at its path. The control receives OwnProps through props and the resolved collection through options.

See Option controls for a custom control and Option values for definition-side inference.

The extraction types read the hidden contract from a control definition:

TypeExtracts
ControlValueOf<Control>Field value type.
ControlOwnPropsOf<Control>Definition-node props type.
ControlOptionOf<Control>One selectable option item.
ControlContextOf<Control>Required form context type.

Use these extraction types for wrappers and integration tests. Application forms usually rely on inference.

Form-kit and binding types

A form kit is the reusable design-system contract. A form binding is one component-local form instance.

const formOptions = {
	defaultValues,
	context: { canEdit: true },
	onSubmit: ({ input, value }) => {
		input.age.toUpperCase() // string
		value.age.toFixed(0) // number
	},
} satisfies UseFormOptions<typeof profileSchema, ProfileContext>
 
function ProfileForm() {
	const form: FormBinding<typeof profileSchema, ProfileContext> =
		profileKit.useForm(profileDefinition, formOptions)
 
	return (
		<profileKit.AutoForm aria-label="Profile" form={form}>
			<profileKit.Submit>Save profile</profileKit.Submit>
		</profileKit.AutoForm>
	)
}
TypePurpose
FormKit<Controls, ...>Kit methods, components, controls, slots, grid, and context contract.
CreateFormKitOptions<Controls, ...>Options accepted by createFormKit.
UseFormOptions<Schema, Context>defaultValues, context, flags, managed update hooks, value middleware, and submit handler.
FormSubmitDetails<Schema, Context>Parsed output, editable input, form binding, and submitter snapshot passed to a submit handler.
FormBinding<Schema, Context>Raw RHF API, managed update, fixed definition, and current context.
FormProps<Schema, Context>Props for kit.Form.

AutoFormProps<Schema, Context> is currently the same contract as FormProps. It is a separate public name for components that wrap kit.AutoForm.

Use form.api from FormBinding for unchanged React Hook Form operations. Use form.update for a managed value transaction. The other public fields are the fixed definition and current context. Read FormBinding on the API page for their behavior.

Managed value types

Use these types when you configure value middleware or create a reusable managed update:

TypePurpose
FormUpdateRecipe<Input>Synchronous Immer recipe accepted by form.update and middleware api.update.
FormMiddleware<Input, Context>Redux-shaped middleware for one form binding.
FormMiddlewareApi<Input>Read current values or start a later managed update.
FormMiddlewareNextForward authoritative patches to the next middleware or terminal.
ValueTransaction<Input, Context>TypeScript-readonly proposal or final committed update with previous values, next values, patches, source, and context. It is not a frozen snapshot.
ValueTransactionSource<Input>Discriminated source for a control, array action, form.update, or optional feature restore.
ValuePatchReadonly Immer operation with a segment-array path.

Middleware can replace the terminal return value. Therefore, form.update and FormMiddlewareNext return unknown. Read Value middleware for runtime rules and examples.

Managed value history types

Import history types from form-please/history:

TypePurpose
CreateHistoryOptionsConfigure retention and control grouping.
HistoryFeatureReusable middleware plus exact-form handle lookup.
HistoryHandle<Input>Navigate, subscribe, clear, export, and import one form's history.
HistorySnapshotCurrent canUndo, canRedo, index, and length.
HistoryOperationResultapplied, unavailable, cancelled, or transformed.
HistoryJournal<Input>Version 1 in-memory input positions and current index.

Read Managed value history for the complete runtime contract.

Form persistence types

Import persistence types from form-please/persistence:

TypePurpose
JsonValueValue accepted by an adapter across its JSON boundary.
PersistenceCodec<Value>Tagged conversion for one explicit opaque value type.
PersistenceMigrationConvert decoded input between application versions.
FormPersistenceAdapterKeyed asynchronous load, save, and remove transport.
CreatePersistenceOptionsConfigure the adapter, key, version, save delay, codecs, migration, and error observer.
PersistenceErrorDetailsIdentifies the failed restore, save, or clear operation.
PersistenceFeatureReusable middleware plus exact-form handle lookup.
PersistenceHandleRestore, start, flush, clear, and observe one form's draft.
PersistenceSnapshotCurrent restore phase and save state.
PersistenceRestoreResultapplied, empty, cancelled, transformed, or conflict.

Read Form persistence for the complete runtime contract.

Slot and structure types

FormKitSlots<FieldOptions, SectionOptions, ArrayOptions> describes the six required slot components. The three option generics set the corresponding slotOptions prop types.

function ErrorMessage({ rootProps, issue }: ErrorMessageSlotProps) {
	return (
		<p {...rootProps} role="alert">
			{issue.path !== undefined && `${issue.path}: `}
			{issue.message}
		</p>
	)
}
 
const controls = createNativeControls()
const slots = {
	...createDefaultSlots(),
	ErrorMessage,
} satisfies FormKitSlots
 
const kitOptions = {
	controls,
	slots,
	grid: [1, 2, 4],
} satisfies CreateFormKitOptions<typeof controls>
 
const customKit = createFormKit(kitOptions)
 
const formStyle = {
	"--fp-row-gap": "1rem",
} satisfies FormPleaseStyle
Slot props typeUsed by
FieldSlotProps<Options>Field slot.
SectionSlotProps<Options>Section slot.
ArraySlotProps<Options>Array slot.
ArrayItemSlotPropsArrayItem slot.
ErrorMessageSlotPropsErrorMessage slot.

SubmitSlotProps<Schema> types the Submit slot and typed render functions. It provides buttonProps, deeply readonly input values, isSubmitting, isDirty, and canSubmit (!isValidating && !isSubmitting).

Shared structure types

TypePurpose
StructuralRootPropsGenerated attributes, style, and ref for a structural root element.
StructuralNodeNameAllowed data-fp-node values for generated structure.
FormPleaseStyleReact CSS properties plus the supported --fp-* variables.
FormIssueNormalized validation issue with message and optional path.

Spread rootProps on the slot's root element. Do not replace its generated attributes or ref. Read Form kits for every slot prop.

Resource state

ResourceState<Value, Error> is a readonly tagged union. Its status is "pending", "success", or "error".

type Countries = ResourceState<readonly string[], Error>
 
function countryCount(resource: Countries) {
	if (resource.status !== "success") return 0
	return resource.value.length
}

The type does not fetch or cache data. Use it to expose application-owned resource state. Read Resource state for mapping and adapter examples.