Skip to content

API reference

Creates a self-contained form store.

Property Type Description
Form Component Renders the form element; accepts the same props as Form
state FormState<M> Reactive state object
store FormStore<M> Raw [state, mutations] tuple

Use useForm when you need to read field values or validity outside the form tree. Use Form directly when you only need a submit handler.

Pass a Standard Schema-compatible schema to infer values without a form generic:

const form = useForm({ schema: loginSchema });
<form.Form onSubmit={(values) => values.email}>{/* fields */}</form.Form>;

A self-contained form that creates its own internal store.

<Form<LoginValues> onSubmit={handleSubmit} errors={['Server error']} isLoading={isPageLoading}>
{/* fields */}
</Form>
<Form schema={loginSchema} onSubmit={(values) => values.email}>
{/* values inferred from loginSchema */}
</Form>
Prop Type Description
onSubmit (values: M) => void | Promise<void> Submit handler
schema StandardSchemaV1 Optional Standard Schema-compatible validator; successful output is passed to onSubmit
children JSX.Element Field components and submit buttons
errors string[] Form-level errors to display
isLoading boolean Disables all fields while loading
isProcessing boolean Controlled override for processing state
className string CSS class on the form element
align 'left' | 'center' Button alignment, defaults to 'left'
fullWidthButtons boolean Stretch buttons to full width

Form and the field components are each generic over the form’s value type (M), and field components are also generic over their own name (N) — but JSX can’t carry a type parameter from <Form<M>> down into a sibling field’s own generic inference, so every element is type-checked in isolation. Spelling out <M, 'name'> on Form and every field works (see InputField below) but gets repetitive for a component that only ever renders one form. Call createForm<M>() once instead to bind M for Form and all field components together:

const { Form, InputField, PasswordField } = createForm<LoginValues>();
<Form onSubmit={(values) => values.email}>
<InputField name='email' label='Email' required />
<PasswordField name='password' label='Password' required minLength={8} />
</Form>;

onSubmit’s values, name='bogus', and a self-referencing match are all checked against M, exactly as if you had written <Form<LoginValues>> and <InputField<LoginValues, 'email'>> at each call site. The returned components are the real Form/InputField/PasswordField/TextAreaField/ CheckboxFieldcreateForm only fixes M at the type level, it does not wrap or change their behavior.

Pass a Standard Schema-compatible schema instead of a type argument to infer M from it, the same as useForm({ schema }):

const { Form, InputField, PasswordField } = createForm({ schema: loginSchema });
<Form onSubmit={(values) => values.email}>
<InputField name='email' label='Email' required />
<PasswordField name='password' label='Password' required />
</Form>;

Fields are typed against the schema’s input (what the DOM gives you); onSubmit receives the schema’s output, which can differ for a transform/coercion schema. The schema also becomes Form’s default, so it does not need to be repeated as a schema prop — though an individual <Form schema={other}> call can still override it, same as useForm.

If several field groups need to share one Form/store — a multi-step form, for example — call createFields<M>() instead to bind just the field components, and wire the shared store up with useForm/FormContextProvider (see below).

The field-only half of createForm<M>(), for when the fields don’t own their Form:

const { InputField, PasswordField } = createFields<LoginValues>();
<InputField name='email' label='Email' required />
<PasswordField name='password' label='Password' required minLength={8} />

Renders a labeled <input type="text">.

<InputField<M, 'email'>
name='email'
label='Email address'
defaultValue='user@example.com'
required
pattern={/^[^@]+@[^@]+$/}
/>
Prop Type Description
name StringKeyOf<M> Field name; must match a key in the form value type
label string Visible label text
defaultValue M[N] Initial value
disabled boolean Disables the input
readonly boolean Makes the input read-only
parse (raw: string) => M[N] Convert DOM string to typed value
format (val: M[N]) => string Convert typed value back to display string
validator CustomValidator<M, N> Custom validation function
required, minLength, maxLength, pattern, min, max, match Constraint props Built-in validation constraints

All standard HTML input attributes are also accepted.

Same props as InputField. Renders <input type="password">.

Same props as InputField, except type. Renders a <textarea>.

<TextAreaField name='bio' label='Bio' maxLength={500} />

Renders a labeled checkbox. The field value in form state is a boolean.

<CheckboxField name='acceptTerms' label='I accept the terms' required />
Prop Type Description
name StringKeyOf<M> Field name
label string Label text
defaultChecked boolean Initial checked state
disabled boolean Disables the checkbox
required boolean Field must be true to be valid
validator CustomValidator<M, N> Custom validation function

Renders a submit button. It is disabled automatically when the form has validation errors.

<SubmitButton>Log in</SubmitButton>
<SubmitButton name='saveDraft'>Save draft</SubmitButton>
<SubmitButton name='publish'>Publish</SubmitButton>

Named submit buttons select a handler from an object-style onSubmit map, for example onSubmit={{ saveDraft, publish }}.

Prop Type Description
children JSX.Element Button label
isDisabled boolean Override disabled state
isFullWidth boolean Stretch to container width
name string Optional field name for multi-button forms
variant 'primary' | 'approve' Visual variant; approve renders type="button"

form.state is reactive. Access it inside Solid signals, createEffect, or JSX to get fine-grained updates.

Property or method Type Description
isFormValid boolean true when no registered field has errors
haveValuesChanged boolean true when any field has changed from its initial value
isLoading boolean Form is in a loading state
isProcessing boolean Async submit handler is in flight
errors string[] Form-level errors, including thrown or rejected submit errors
getFieldValue(name) M[N] | undefined Current parsed value for a field
getFieldErrors(name) string[] | undefined Current errors for a field
isFieldValid(name) boolean | undefined false if the field has errors; undefined if not registered
hasFieldBeenValid(name) boolean | undefined true once the field has been error-free
hasFieldBlurred(name) boolean | undefined true once the user has blurred the field
hasFieldChanged(name) boolean | undefined true once the field value has changed
hasFieldBeenInitialized(name) boolean true once the field has registered with the store

The package exports StandardSchemaV1, StandardSchemaV1Types, InferStandardSchemaInput, InferStandardSchemaOutput, StandardSchemaFormValues, and StandardSchemaSubmitValues for custom schema producers and helpers. StandardSchemaFormValues is the schema input type used by field state; StandardSchemaSubmitValues is the schema output type passed to onSubmit.