Multi Page Form Wizards

A wizard in Form.io is not a separate form type. It is a rendering mode. The same JSON schema that renders as a single-page web form can render as a multi-page wizard by changing one property: display: "wizard". This matters because it means you can switch between wizard and web form modes without rebuilding your form, and all the underlying validation, conditional logic, and data structure remain identical.

The wizard renderer interprets root-level Panel components as pages. Each Panel becomes a separate step with its own navigation. The JSON schema stays flat. The rendering engine transforms it into a paginated experience.

{
  "title": "Registration",
  "type": "form",
  "display": "wizard",
  "components": [
    {
      "type": "panel",
      "title": "Personal Information",
      "key": "page1",
      "components": [...]
    },
    {
      "type": "panel", 
      "title": "Contact Details",
      "key": "page2",
      "components": [...]
    },
    {
      "type": "panel",
      "title": "Review & Submit",
      "key": "page3",
      "components": [...]
    }
  ]
}

Change display from “wizard” to “form” and the same schema renders as three visible panels stacked vertically on a single page. The components, validation rules, and conditional logic do not change.

Creating Wizards in the Form Builder

In the Form.io portal, you create a wizard by selecting “Web Form” and then switching the “Display As” dropdown from “Webform” to “Wizard” while in the builder. The interface transforms immediately. All existing components move into a default first page Panel.

To add pages, you add Panel components. Each Panel at the root level becomes a wizard step. The Panel’s title becomes the page label shown in the breadcrumb navigation. To move components between pages, switch “Display As” back to “Webform” temporarily. The pages separate into visible Panels where you can drag components between them. Switch back to “Wizard” mode to restore the paginated view.

This toggle between rendering modes is useful during development. Seeing all fields at once makes it easier to configure cross-page conditional logic and calculated values. Seeing the wizard view lets you test the user flow.

Navigation and Button Configuration

When rendered, wizards display navigation buttons: Cancel, Previous, Next, and Submit (on the final page). The breadcrumb bar at the top shows all pages and allows direct navigation by clicking page titles.

You control these behaviors through renderer options:

Formio.createForm(document.getElementById('wizard'), 'https://myproject.form.io/registration', {
  breadcrumbSettings: { clickable: false },
  buttonSettings: { 
    showCancel: false,
    showPrevious: true,
    showNext: true 
  }
});

Setting breadcrumbSettings.clickable to false forces linear progression. Users must use Next/Previous buttons and cannot jump between pages. This is useful for workflows where earlier pages collect data that determines later page content, and you do not want users skipping ahead.

Each Panel component also has button settings in its configuration that can override these defaults on a per-page basis, though support for granular per-page button control varies by version. Check the formio.js changelog for updates on this capability.

Validation Per Page

Wizard validation occurs at page transitions, not just at final submission. When a user clicks “Next,” the renderer validates all fields on the current page. If validation fails, the user cannot proceed. Invalid fields highlight, and error messages display.

This is the same validation engine used in standard web forms. Required fields, pattern matching, min/max constraints, and custom validation all execute on the current page’s components before allowing navigation.

For workflows requiring server-side validation before page transitions, the beforeNext hook intercepts the navigation:

Formio.createForm(document.getElementById('wizard'), formUrl, {
  hooks: {
    beforeNext: (currentPage, submission, next) => {
      // Perform async validation or API call
      fetch('/validate-step', {
        method: 'POST',
        body: JSON.stringify(submission.data)
      })
      .then(response => {
        if (response.ok) {
          next(); // Allow navigation
        } else {
          next({ message: 'Validation failed on server' });
        }
      });
    }
  }
});

Calling next() with no arguments allows navigation. Calling next(error) blocks navigation and displays the error.

Conditional Pages

Wizard pages can be conditionally shown or hidden based on data entered in previous pages. This uses the same conditional logic system as field visibility, applied to the Panel component representing the page.

A common pattern: the first page asks a classification question. Based on the answer, different subsequent pages appear. In a loan application, selecting “Business Loan” might show pages for business documentation, while “Personal Loan” shows different pages for personal financial information.

Configure this by opening the Panel’s component settings, navigating to the Conditional tab, and setting conditions based on other field values. When the wizard renders, it evaluates these conditions and includes or excludes pages from the navigation flow dynamically.

// Panel conditional configuration in JSON
{
  "type": "panel",
  "title": "Business Documentation",
  "key": "businessDocs",
  "conditional": {
    "show": true,
    "when": "loanType",
    "eq": "business"
  },
  "components": [...]
}

The breadcrumb updates in real-time. If a user changes their answer on page one, the visible pages in the breadcrumb adjust accordingly. The conditional wizard example demonstrates this behavior.

Wizard Events

The wizard emits events at key points in the user flow. Listen for these to integrate with your application:

Formio.createForm(document.getElementById('wizard'), formUrl)
  .then(function(wizard) {
    wizard.on('nextPage', function(page) {
      console.log('Moving to page:', page.page);
      // Track progress, update UI, log analytics
    });
    
    wizard.on('prevPage', function(page) {
      console.log('Going back to page:', page.page);
    });
    
    wizard.on('submit', function(submission) {
      console.log('Final submission:', submission);
    });
    
    wizard.on('change', function(changed) {
      // Fires on any field change within the wizard
    });
  });

These events enable progress indicators, analytics tracking, or triggering external actions at specific workflow points.

Draft Saving in Wizards

Long wizards benefit from draft saving. If a user leaves mid-way through a 10-page wizard, they should be able to return and continue from where they left off. Form.io’s Auto Save Forms feature handles this.

Enable automatic draft saving when rendering the wizard:

Formio.createForm(document.getElementById('wizard'), formUrl, {
  saveDraft: true,
  saveDraftThrottle: 10000 // Save every 10 seconds
});

Drafts are associated with authenticated users. When the same user returns to the form, the renderer automatically restores their draft state, including which page they were on and all entered data. Anonymous users cannot use draft saving because there is no identity to associate the draft with.

For manual draft saving, add a “Save as Draft” button component with Action set to “Save State” and State set to “draft.” This lets users explicitly save progress rather than relying on automatic intervals.

Nested Wizards

Complex workflows sometimes require wizards within wizards. A parent wizard handles the main flow, and a specific page contains an embedded child wizard for a sub-process.

When you embed a wizard-display form as a Nested Form component inside another wizard, by default both wizards’ pages merge into a single breadcrumb. This is usually not the desired behavior. The Nested Wizard Forms documentation explains how to use Panel components as containers to keep the child wizard’s navigation separate, appearing as a sub-navigation within the parent page.

This pattern is useful for modular form design. You might have a reusable “Address Collection” wizard that you embed within multiple parent forms, each parent having its own overall workflow while delegating address capture to the standardized embedded wizard.

When to Use Wizards vs. Single-Page Forms

Use wizards when:

  • The form has more than 10-15 fields and would feel overwhelming on a single page
  • Logical groupings exist that correspond to user mental models (personal info, then payment, then review)
  • Later sections depend on earlier answers (conditional pages)
  • Users might need to save progress and return later
  • Mobile users are a significant portion of your audience (smaller pages scroll less)

Use single-page web forms when:

  • The form is short (under 10 fields)
  • Users benefit from seeing all fields simultaneously for context
  • The form will be printed or exported and needs to appear as a single document
  • There are no logical groupings that warrant separation

You can change between modes at any time without data loss. The submission structure remains identical regardless of display mode.

Wizards and PDF Output

When you export a wizard submission to PDF, the PDF renders all pages sequentially as a single document. The page breaks from the wizard do not automatically translate to PDF page breaks. The PDF Template Designer allows you to control PDF layout independently of wizard structure.

Similarly, when viewing submission data in the portal Data tab or through the API, you see the complete flattened submission regardless of how many wizard pages collected the data. The wizard is purely a user experience layer over the same underlying data structure.

Wizards and Form Revisions

If you enable Form Revisions on a wizard form, each revision captures the complete component schema including all Panel configurations. If you add, remove, or reorder wizard pages, the revision history tracks those changes. Submissions remain associated with the revision against which they were captured, ensuring historical submissions render correctly even if the wizard structure has changed since.

Performance Considerations

Wizards load the entire form schema upfront, then show/hide pages client-side. This means the initial load includes all pages, even those the user may never see due to conditional logic. For very large wizards (50+ pages with many conditional branches), consider whether all pages truly need to be in a single form or if your workflow would benefit from separate forms linked through your application logic.

The validation engine runs against the visible page’s components on each navigation, not the entire form. This keeps page transitions responsive even in large wizards.


Related Resources: