Build wizard-style multi-step forms with sections.
A schema with multiple sections automatically renders as a wizard-style multi-step form. The engine manages navigation, progress tracking, and per-section validation.
Defining Sections
const schema: FormEngineSchema = {
id: "onboarding",
version: "1.0.0",
title: "Onboarding",
submitAction: { type: "callback" },
sections: [
{
id: "personal",
title: "Personal Information",
description: "Tell us about yourself",
questions: [
{ id: "name", type: "short_text", label: "Full Name", required: true },
{ id: "dob", type: "date", label: "Date of Birth" },
],
},
{
id: "contact",
title: "Contact Details",
questions: [
{ id: "email", type: "email", label: "Email", required: true },
{ id: "phone", type: "phone", label: "Phone" },
],
},
{
id: "preferences",
title: "Preferences",
questions: [
{ id: "newsletter", type: "boolean", label: "Subscribe to newsletter?" },
],
},
],
};
Section Properties
| Property | Type | Required | Description |
|---|
id | string | Yes | Unique section identifier |
title | string | Yes | Displayed in progress UI |
description | string | No | Shown below the title |
showIf | ConditionExpression | No | Conditional section visibility |
onExit | SectionExitAction | No | Branching / jump rules |
questions | Question[] | Yes | Fields in this section |
Navigation
The engine exposes three navigation methods:
const engine = createEngine(schema);
// Move forward (validates current section first)
engine.nextSection();
// Move backward
engine.prevSection();
// Jump to a specific section by ID
engine.jumpTo("preferences");
Navigation State
The form state includes everything the UI needs to render navigation controls and progress indicators:
const state = engine.getState();
state.currentSectionId; // "contact"
state.currentSectionIndex; // 1 (0-based)
state.visibleSectionIds; // ["personal", "contact", "preferences"]
state.visitedSectionIds; // ["personal", "contact"]
state.totalVisibleSections; // 3
state.progressPercent; // 66 (visited / total * 100)
state.isCurrentSectionValid; // true/false
Section Validation
Before navigating forward, you can validate the current section. The engine checks all visible, non-structural fields:
// Validate a specific section
const result = engine.validateSection("personal");
result.valid; // boolean
result.errors; // Record<string, string[]>
result.firstErrorFieldId; // "name" (first field with errors)
result.firstErrorSectionId; // "personal"
// Validate the entire form
const fullResult = engine.validate();
Hidden fields (those with a showIf that evaluates to false) and structural fields (info_block, consent) are skipped during section validation.
Branching with Exit Rules
Use onExit to define conditional jumps when leaving a section. Rules are evaluated in order — the first matching rule wins.
{
id: "triage",
title: "Triage",
questions: [
{
id: "issue_type",
type: "single_select",
label: "What do you need help with?",
options: [
{ label: "Billing", value: "billing" },
{ label: "Technical", value: "technical" },
{ label: "Other", value: "other" },
],
},
],
onExit: {
rules: [
{
condition: { field: "issue_type", operator: "eq", value: "billing" },
jumpTo: "billing_details",
},
{
condition: { field: "issue_type", operator: "eq", value: "technical" },
jumpTo: "technical_details",
},
],
default: "general_details", // Fallback if no rule matches
},
}
Jump Rule Resolution
- Evaluate each rule's condition in order.
- If a condition matches, jump to its
jumpTo section. - If no rules match and
default is set, jump to the default section. - If no rules match and no default, proceed to the next sequential visible section.
Conditional Sections
Sections with showIf are automatically skipped when their condition is false. Navigation, progress, and validation all respect section visibility.
{
id: "employer_info",
title: "Employer Information",
showIf: { field: "employment_status", operator: "eq", value: "employed" },
questions: [
{ id: "company", type: "short_text", label: "Company Name", required: true },
],
}
Progress Tracking
Configure progress display in the schema settings:
settings: {
showProgress: true,
progressStyle: "steps", // "bar" | "steps" | "percentage"
showSectionNumbers: true,
}
Progress is calculated as visitedSections / totalVisibleSections * 100. Only visible sections count — hidden sections are excluded from both the count and the progress bar.
Navigation Settings
settings: {
navigation: {
showBack: true, // Show "Back" button
showSectionList: true, // Show section list/sidebar
nextLabel: "Continue", // Custom label for "Next"
backLabel: "Go Back", // Custom label for "Back"
allowSkip: false, // Allow skipping sections
},
}