T://bendet

FormGenerator

creation : FormGenerator

it’s alive!recipedistilled

An entry from the chronicle of built things — its purpose, its materials, and the bargains struck along the way.

Provenance first: FormGenerator began as a hiring exercise and outgrew it. It takes a JSON schema describing fields and renders a working form — validation, conditional visibility, error announcement and focus management included — with no form library and no validation library underneath. Hand-rolling both is the point; the data modeling is the exhibit.

Fields are a discriminated union on their type, so an invalid schema does not compile — a select without options is unrepresentable — and the renderer narrows per case with no casts anywhere. The same compile-time-checklist trick appears three times over: the renderer's switch, the validation registry and the visibility operator dispatch each assign an unhandled case to never, so adding a field type, a rule or an operator stays a type error until every consumer handles it.

The engine is a headless hook. It owns state, validation timing and submission, and is exported on its own; the shipped component is only the default renderer over it. Fields the user cannot interact with — hidden by a visibility condition, or carrying a type the renderer does not know — are skipped by validation and dropped from the payload, so an invisible required field can never block a submit with nothing on screen to fix.

tech_stackInventory of Materials

  • React 19Interface
  • TypeScriptLanguage
  • ViteBuild
  • Tailwind CSSStyling
  • VitestTests
The demo form — full name, email, password, role, short bio and a terms checkbox — rendered beside a panel printing the live form state as JSON.
SYS_VIEW: a_​schema_​rendered_​as_​a_​form_​beside_​its_​live_​statePlate I. A schema rendered as a form, beside its live state.
The same form after submitting it empty, every required field outlined in red beneath the message This field is required.
SYS_VIEW: validation_​raised_​only_​after_​the_​first_​submitPlate II. Validation, raised only after the first submit.
The form with Role set to Manager, which reveals a Team size field that was not rendered a moment earlier, while the state panel shows role now reading manager.
SYS_VIEW: a_​field_​revealed_​by_​a_​visibility_​conditionPlate III. A field revealed by a visibility condition.

Engineering Tradeoffs

The submit payload is typed as an open record rather than inferred per schema. Precise payload types need the schema as a compile-time literal, and these schemas arrive as runtime data — a wider honest type beats a narrower dishonest one.

Every schema failure fails open. An invalid regex, an unknown visibility operator or a duplicate field id warns and lets the form render, because a schema typo must not brick a form in production. The cost is that a broken schema is quiet — the console warning is the only signal, and nobody reads the console.

Validators receive the whole rules object rather than only their own config. TypeScript cannot correlate a registry entry with its config through a union key without a cast, and this codebase has none — the looser validator signature is what buys zero casts everywhere else.

View live (opens in new tab)Source on GitHub (opens in new tab)