Core concepts / Multi-step forms

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:

ModeBehaviour
classicAll visible sections render on one scrollable page. A single Submit button at the bottom.
steppedOne section at a time. Next/Back buttons. Progress bar. (default)
conversationalOne question at a time. Enter key to advance. Question-level progress.
settings: {  displayMode: 'stepped',  showProgress: true,  progressStyle: 'bar',}

For a full guide on each mode with examples, see Display modes.

In stepped mode, the engine manages navigation state:

// FormState navigation propertiescurrentSectionId: stringcurrentSectionIndex: number         // Among visible sectionstotalVisibleSections: numberprogressPercent: number             // 0–100visibleSectionIds: string[]visitedSectionIds: string[]canGoNext: booleancanGoPrev: booleanisCurrentSectionValid: boolean
const engine = createEngine(schema)engine.nextSection()        // Move to next visible sectionengine.prevSection()        // Move to previous sectionengine.jumpTo('section-id') // Jump directly to a section

nextSection() validates the current section before advancing. If validation fails, the user stays on the current section and errors are displayed.

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'}
StyleWhat it shows
barA 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:

  • showIf controls whether a section exists in the form
  • onExit controls 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