BlogWork With Me →
Back to Blog

Build a Multi-Step Survey in 5 Minutes with FieldCraft

What We're Building

A three-step customer feedback survey with:

  • Step 1: Contact info with email validation
  • Step 2: Product feedback with ratings and conditional follow-up
  • Step 3: Final comments with optional contact consent

Progress bar, back navigation, per-section validation, and conditional fields — all defined in one JSON schema. No useState, no form libraries, no validation wiring.

Setup

Install FieldCraft's core engine and React renderer:

npm install @squaredr/fieldcraft-core @squaredr/fieldcraft-react

The Schema

FieldCraft forms are defined as JSON schemas. A multi-step form uses the sections array — each section becomes one step.

import type { FormEngineSchema } from "@squaredr/fieldcraft-core";

const surveySchema: FormEngineSchema = {
id: "customer-survey",
version: "1.0.0",
title: "Customer Feedback Survey",
description: "Help us improve — takes about 2 minutes.",
settings: {
showProgress: true,
progressStyle: "steps",
navigation: {
showBack: true,
allowSkip: false,
},
},
submitAction: { type: "callback" },
sections: [
{
id: "contact",
title: "About You",
description: "So we know who's giving feedback",
questions: [
{
id: "name",
type: "short_text",
label: "Your Name",
required: true,
placeholder: "Jane Smith",
},
{
id: "email",
type: "email",
label: "Email Address",
required: true,
placeholder: "jane@company.com",
helpText: "We'll only contact you about your feedback",
},
{
id: "role",
type: "single_select",
label: "Your Role",
required: true,
options: [
{ label: "Developer", value: "developer" },
{ label: "Designer", value: "designer" },
{ label: "Product Manager", value: "pm" },
{ label: "Executive", value: "exec" },
{ label: "Other", value: "other" },
],
},
],
},
{
id: "feedback",
title: "Your Feedback",
description: "Tell us what you think",
questions: [
{
id: "satisfaction",
type: "rating",
label: "How satisfied are you with the product?",
required: true,
},
{
id: "recommend",
type: "nps",
label: "How likely are you to recommend us?",
required: true,
},
{
id: "best_feature",
type: "single_select",
label: "What do you like most?",
required: false,
options: [
{ label: "Ease of use", value: "ease" },
{ label: "Performance", value: "performance" },
{ label: "Documentation", value: "docs" },
{ label: "Support", value: "support" },
{ label: "Pricing", value: "pricing" },
],
},
{
id: "pain_point",
type: "long_text",
label: "What's your biggest pain point?",
required: false,
placeholder: "Tell us what frustrates you...",
validation: [{ type: "maxLength", value: 500 }],
},
],
},
{
id: "closing",
title: "Final Thoughts",
description: "Anything else?",
questions: [
{
id: "improvement",
type: "long_text",
label: "If you could change one thing, what would it be?",
required: false,
placeholder: "Your suggestion...",
validation: [{ type: "maxLength", value: 1000 }],
},
{
id: "can_contact",
type: "boolean",
label: "Can we follow up with you about your feedback?",
required: false,
},
{
id: "preferred_contact",
type: "single_select",
label: "How should we reach you?",
required: true,
options: [
{ label: "Email", value: "email" },
{ label: "Phone", value: "phone" },
{ label: "Slack", value: "slack" },
],
showIf: {
field: "can_contact",
operator: "eq",
value: true,
},
},
],
},
],
};

That's the entire form definition. Let's break down what's happening.

Sections = Steps

Each object in the sections array becomes one step in the multi-step flow. The engine handles navigation, progress tracking, and per-section validation automatically.

sections: [
{ id: "contact", title: "About You", questions: [...] },
{ id: "feedback", title: "Your Feedback", questions: [...] },
{ id: "closing", title: "Final Thoughts", questions: [...] },
]

Users see "About You" first. They can't advance to "Your Feedback" until all required fields in the current section pass validation. The back button lets them revisit previous steps without losing data.

Settings Control the UX

settings: {
showProgress: true,
progressStyle: "steps", // "steps", "bar", or "percentage"
navigation: {
showBack: true, // Show back button
allowSkip: false, // Can't skip sections
},
},

Change progressStyle to "percentage" to show "67% complete" instead of "Step 2 of 3". Change "bar" for a visual progress bar. All three styles work with zero additional code.

Validation

Required fields get validated automatically. For custom rules, add a validation array:

{
id: "pain_point",
type: "long_text",
label: "What's your biggest pain point?",
validation: [{ type: "maxLength", value: 500 }],
}

Built-in validators include minLength, maxLength, min, max, pattern (regex), and email. The email validator checks for a valid TLD — user@example.c is rejected, user@example.co is accepted.

Errors display on blur, not on every keystroke. Users finish typing before seeing validation feedback.

Conditional Fields

The showIf property controls field visibility based on other field values:

{
id: "preferred_contact",
type: "single_select",
label: "How should we reach you?",
showIf: {
field: "can_contact",
operator: "eq",
value: true,
},
}

The "preferred contact" field only appears when the user toggles "Can we follow up?" to yes. The engine re-evaluates conditions on every value change and handles cleanup — if a user fills in the conditional field, then toggles the parent back to "no", the hidden field's value is cleared.

Operators available: eq, neq, gt, lt, gte, lte, contains, notContains, startsWith, endsWith, in, notIn, isEmpty, isNotEmpty, and more. You can nest conditions with AND/OR groups for complex logic.

Rendering

One component, one prop for the schema, one callback for submission:

import { FormEngineRenderer } from "@squaredr/fieldcraft-react";
import "@squaredr/fieldcraft-react/styles.css";

export default function SurveyPage() {
const handleSubmit = (response) => {
console.log(response.values);
// POST to your API, save to database, etc.
};

return (
<FormEngineRenderer
schema={surveySchema}
onSubmit={handleSubmit}
/>
);
}

The response.values object contains all field values keyed by field ID:

{
"name": "Jane Smith",
"email": "jane@company.com",
"role": "developer",
"satisfaction": 4,
"recommend": 9,
"best_feature": "ease",
"pain_point": "Documentation could be more detailed",
"improvement": "Add more code examples",
"can_contact": true,
"preferred_contact": "email"
}

Draft Persistence

Long surveys risk abandonment. FieldCraft auto-saves progress to localStorage by default. If a user closes the tab and comes back within 72 hours, their answers are restored — including which section they were on.

No configuration needed. It just works.

Theming

Add a theme prop to change the look:

<FormEngineRenderer
schema={surveySchema}
theme="dark"
onSubmit={handleSubmit}
/>

Built-in themes: clean, dark, modern, high-contrast, clinical, playful. Or pass a custom theme object for full control over colors, spacing, borders, and typography.

Using a Pre-Built Template

Don't want to write the schema from scratch? FieldCraft ships 16 free templates:

npm install @squaredr/fieldcraft-templates-free
import { feedbackSurveySchema } from "@squaredr/fieldcraft-templates-free";

<FormEngineRenderer
schema={feedbackSurveySchema}
onSubmit={handleSubmit}
/>

That gives you a three-section feedback survey with NPS, Likert scales, ratings, conditional follow-up, and progress tracking — zero configuration.

Browse all 16 templates on npm or check the template catalog.

What's Next

This tutorial covers the basics. FieldCraft also supports:

  • Computed fields — calculate values from other fields using expressions
  • Custom validators — register your own validation logic
  • Custom field types — build and register field components
  • Storage adapters — save responses to Postgres, Supabase, or any webhook endpoint
  • File uploads — with drag-and-drop and preview

Full docs at squaredr.tech/products/fieldcraft/docs.

Links