Forms I no longer dread
Formik and Yup got me most of the way; Zod and a shared schema finished the job.
For two years every form I built was a small negotiation between the design, the API and whatever validation library I had reached for that month. Formik and Yup made the negotiation shorter. A shared Zod schema ended it.
One schema, two sides
The turning point was putting the schema in a file both the client and the server import. The form validates as you type against the same rules the endpoint enforces, so the error you see in the browser is the error the API would have sent anyway - just earlier, and in nicer words.
Say what went wrong
Most of the dread was never about validation logic. It was about the copy. “Invalid input” tells nobody anything. The rule I hold to now: every error message names the field, says what was expected, and where possible says how to fix it.
A form that says what went wrong is doing half the support team’s job for them.
The boring states
Submitting, submitted, failed with retry, and the one nobody designs: the network dropped halfway. Draw all four before writing a line of JSX and the form will feel solid no matter what the library is.