@k8ordo/form

Troubleshooting

Common symptoms, what causes them, and how to fix them.

On this page

parseForm throws that a field is missing from the FormData

Cause

A field in the schema never arrived in the submitted FormData. Usually its input was never spread, or the field is not rendered under some condition.

Fix

Spread input onto every field, and always render the fields inside an .optional() object. Radios, <select> and checkboxes, which send nothing when left alone, are exempt.

A field marked .optional() still rejects an empty value

Cause

An empty text field submits "", not nothing. .optional() only allows a missing value, so a check such as .min(1) still applies to "".

Fix

Write a schema that accepts "", such as z.union([z.literal(''), z.string().min(3)]). The .min(3) then sits inside a union whose branch cannot be picked, so the browser does not check it and it is listed in dropped.

With zod/mini, every message reads “Invalid input”

Cause

zod/mini does not load a message locale by default, to keep the bundle small. Without one, every error reads Invalid input.

Fix

Load a locale with z.config(z.locales.en()) in a module the server loads first, or write a message on each check.

A radio selection is lost after a failed submission

Cause

The enum’s input is spread onto hand-written radios. After a submission input carries the previous choice as defaultValue, which collides with each radio’s own value.

Fix

Take only name and required from input, and set defaultChecked per option from state.values. @k8ordo/ui’s Radio and RadioCard take the spread as it is.

Focus does not move when the same failure comes back

Cause

useForm tells responses apart by their content and token. A hand-built state without a fresh token makes a second identical failure look like the same response.

Fix

Build on the state that parseForm returned, or give a hand-built state a fresh token every time.

Focus does not reach an error in a hidden step

Cause

A field inside a hidden step cannot take focus.

Fix

When a new state arrives, switch to the step holding the error during render. See “Multi-step forms”.

A stringbool checkbox submits “on”

Cause

A spread value does not reach @k8ordo/ui’s Checkbox, so the box submits the browser’s default on.

Fix

Render a z.stringbool() field as a plain <input {...field.input} />.

NumberField’s range error is not worded like zod

Cause

NumberField renders type="text", so the browser does not check min and max. It reports an out-of-range value itself, in @k8ordo/ui’s wording.

Fix

To keep zod’s wording, spread the derived type="number" onto a TextField. The browser checks the range, and useForm shows zod’s message.

k8ordo

React libraries that use Baseline features without holding back.

© 2026 k8o — MIT License

Typeset in Noto Sans JP & M PLUS 2