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