Multi-step forms
Split forms into sections with step navigation, progress tracking, per-step validation, and conditional branching.
Display modes
The settings.displayMode property controls how sections are rendered:
| Mode | Behaviour |
|---|---|
classic | All visible sections render on one scrollable page. No navigation buttons. |
stepped | One section at a time. Next/Back buttons. Progress indicator. |
conversational | One question at a time. Typeform-style. |
settings: {
displayMode: 'stepped',
showProgress: true,
progressStyle: 'bar',
}Navigation
In stepped mode, the engine manages navigation state:
// FormState navigation properties
currentSectionId: string
currentSectionIndex: number // Among visible sections
totalVisibleSections: number
progressPercent: number // 0–100
visibleSectionIds: string[]
visitedSectionIds: string[]
canGoNext: boolean
canGoPrev: boolean
isCurrentSectionValid: booleanNavigation methods
const engine = createEngine(schema)
engine.nextSection() // Move to next visible section
engine.prevSection() // Move to previous section
engine.jumpTo('section-id') // Jump directly to a sectionnextSection() validates the current section before advancing. If validation fails, the user stays on the current section and errors are displayed.
Navigation settings
settings: {
navigation: {
showBack: true, // Show "Back" button
showSectionList: false, // Show sidebar section list
nextLabel: 'Continue', // Custom button labels
backLabel: 'Go Back',
allowSkip: false, // Allow skipping to any section
},
}Progress tracking
Three progress styles are available:
settings: {
showProgress: true,
progressStyle: 'bar', // 'bar' | 'steps' | 'percentage'
}| Style | What it shows |
|---|---|
bar | A horizontal progress bar showing percent complete |
steps | "Step 2 of 5" counter |
percentage | "40% complete" text |
Progress is calculated based on the current section index relative to total visible sections. Hidden sections (where showIf is false) are excluded from the count.
Per-section validation
When the user clicks "Next", the engine validates only the current section's visible fields. If any field has errors, navigation is blocked and errors are displayed.
const result = engine.validateSection('contact-details')
// { valid: false, errors: { email: ['Enter a valid email.'] }, firstErrorFieldId: 'email' }On submit, the engine validates all visible fields across all sections.
Conditional sections
Sections with showIf are dynamically included or excluded from navigation:
{
id: 'developer_questions',
title: 'Developer Experience',
showIf: { field: 'role', operator: 'eq', value: 'developer' },
questions: [/* ... */],
}If the user selects "Developer", this section appears in the navigation flow. If they change their answer, it disappears — and if they were currently on it, the engine moves them to the first visible section.
Jump rules (branching)
Use onExit on a section to create branching flows:
{
id: 'triage',
title: 'Triage',
questions: [/* severity selector */],
onExit: {
rules: [
{
condition: { field: 'severity', operator: 'eq', value: 'critical' },
jumpTo: 'escalation',
},
{
condition: { field: 'severity', operator: 'eq', value: 'low' },
jumpTo: 'self_service',
},
],
default: 'general_support',
},
}Rules are evaluated in order — first match wins. If no rule matches and no default is set, the next section in array order is used.
Jump rules and showIf work together but serve different purposes:
showIfcontrols whether a section exists in the formonExitcontrols which section comes next when leaving
Using hooks for navigation UI
The useSectionProgress hook provides reactive navigation state for custom UI:
import { useSectionProgress } from '@squaredr/fieldcraft-react'
function ProgressHeader({ engine }) {
const progress = useSectionProgress(engine)
return (
<div>
<p>Step {progress.currentSectionIndex + 1} of {progress.totalVisibleSections}</p>
<div style={{ width: `${progress.progressPercent}%` }} />
</div>
)
}Next steps
- FormRenderer — the main component and its props
- Hooks — useFormEngine, useFieldValue, useSectionProgress
- Drafts & prefill — persist answers across sessions