Build a Form Builder / Dynamic Forms Engine

“Build a form builder” or “build a dynamic forms engine” is a senior+ frontend interview that probes whether you can design a system that renders forms from a declarative spec. Used by Typeform, Google Forms, JotForm, Survey-style builders, and admin tools that need user-defined forms. The interview tests state management, validation, conditional logic, and the patterns that scale to hundreds of fields.

Clarify scope

  • Form rendering only or full builder (drag-drop UI to compose forms)?
  • What field types? (text, select, multi-select, date, file, signature)
  • Conditional / branching logic?
  • Multi-step / wizard?
  • Save partial responses?

The schema

{
  id: "feedback-form",
  fields: [
    { id: "name", type: "text", label: "Your name", required: true },
    { id: "rating", type: "radio", label: "Rating",
      options: [
        { value: "1", label: "1" }, { value: "2", label: "2" },
        ... { value: "5", label: "5" }
      ]},
    { id: "comments", type: "textarea", label: "Comments",
      condition: { field: "rating", op: "lessThan", value: "4" } }
  ]
}

JSON Schema is a standard alternative; many tools use it as the spec.

Rendering engine

  • Iterate fields; render each based on type
  • Type registry: { text: TextField, select: SelectField, ... }
  • Easy to extend with new field types
  • Each field reads/writes from a single form state object

State management

  • Use react-hook-form, Formik, or roll your own
  • react-hook-form is the modern default — uncontrolled, fast, small
  • State shape: Map<fieldId, value>
  • Validation runs on change / blur / submit (configurable)

Validation

  • Per-field rules: required, minLength, pattern, custom function
  • Cross-field rules: “passwords match”, “end date after start date”
  • Library: Zod, Yup, Valibot — define schema, validate against it
  • Async validation (e.g., username availability) with debouncing
  • Error display below each field; summary at top for accessibility

Conditional fields

Show/hide fields based on other field values:

  • Each field has an optional condition
  • Renderer evaluates condition against current state
  • Hidden fields: do not collect data; do not validate
  • Animation when fields appear / disappear

Multi-step / wizard

  • Group fields into steps
  • Validate current step before advancing
  • Progress indicator
  • Allow back-navigation; preserve state
  • Optional: save partial response on each step (resume later)

File upload

  • Drag-and-drop zone or click-to-select
  • Multi-file with previews
  • Validation: type, size
  • Direct-to-S3 upload with signed URL
  • Show progress; allow retry / cancel

The “form builder” UI (if in scope)

  • Drag fields from a palette into the form
  • Click a field to edit its config (label, options, validation)
  • Preview tab shows the rendered form
  • Save outputs the JSON schema
  • Versioning: keep old versions for forms that have responses

Submission

  • Validate full form on submit
  • Show inline errors for each invalid field
  • Loading state during submission
  • Success: redirect or inline confirmation
  • Failure: clear retry path; do not lose data

Accessibility

  • Each field has a visible <label> associated via htmlFor
  • Required fields marked with aria-required and visible asterisk
  • Errors linked via aria-describedby and aria-invalid
  • Validation summary at top, focused on submit failure (jumps screen-reader user to errors)
  • Keyboard: tab through in order; submit on Enter from text inputs (not textarea)

Save and resume

  • Persist current values to localStorage on each change (debounced)
  • Restore on mount
  • For logged-in users: server-side save with periodic sync
  • “Resume where you left off” UX

Performance considerations

  • For 100+ field forms, virtualize the field list if multi-step is not used
  • Memoize field components by ID
  • Avoid re-rendering whole form on every keystroke (uncontrolled inputs help)
  • Defer validation that requires server calls (debounce)

Edge cases interviewers love

  • Cyclic conditional logic (A depends on B depends on A) — detect and surface
  • User edits the schema while a form has in-progress responses — versioning
  • Network drops mid-submission — queue and retry, do not lose data
  • File upload completes but submission fails — keep file reference, retry submit
  • Partial state from localStorage corrupted — graceful reset

What separates senior from staff

Senior implementations build a static form. Staff implementations handle the schema-driven rendering, conditional fields, multi-step, and persistence. Principal candidates discuss the form-versioning story, the integration with backend schemas (Zod, JSON Schema), and the AI-assisted form-builder where users describe a form in natural language.

Frequently Asked Questions

Library options for the rendering layer?

react-hook-form + zod is the modern stack. Tanstack Form is newer and growing. Formik is legacy.

Should I store form definitions in code or DB?

Static forms (signup, settings): code. User-defined forms (Typeform, Google Forms): DB with JSON schema.

What about field libraries with built-in components?

For interview, build the field types yourself. For production, react-jsonschema-form or @rjsf/core renders forms from JSON Schema with reasonable defaults.

Scroll to Top