Skip to main content
New to FormEngine? It’s a React library that renders forms from JSON schemas — free, MIT-licensed. Start here →
TL;DR: Add a Wizard component to your FormEngine JSON schema, nest WizardStep children inside it, and set validateOnNext: true. FormEngine handles navigation, per-step validation, and calls onFinish with all field data when the user completes the last step. Last reviewed: April 2026. This tutorial builds a 3-step registration form using FormEngine’s Wizard and WizardStep components — all configured through a JSON schema. By the end you’ll have a working wizard with per-step validation, a conditional field, and a submit handler. What you’ll build: a user registration wizard with three steps: Personal Info, Account Setup, and a Confirmation summary. Prerequisites: Completed the Dynamic Form from JSON tutorial or have FormEngine Core installed. ~20 minutes. FormEngine Core is open-source on GitHub. If you need a visual wizard builder for your end-users, see FormEngine Designer.

How the Wizard component works

FormEngine’s Wizard is a JSON-configurable layout component from the @react-form-builder/components-rsuite package. It renders a step-by-step form with:
  • Step navigation — next/previous/finish buttons rendered automatically
  • Per-step validation — set validateOnNext: true to block forward progress until the current step passes validation
  • Full validation on finish — set validateOnFinish: true to recheck all steps before completing
  • Finish action — hook into the wizard’s onFinish event to submit collected data
The form data is flat across all steps — fields in step 1, 2, and 3 all write to the same form.data object.

1. Install dependencies

npm install @react-form-builder/core @react-form-builder/components-rsuite
npm install rsuite
Add the RSuite stylesheet to your entry file (main.tsx or index.tsx):

2. Define the Wizard schema

Create src/registrationSchema.json. The top-level component is a Wizard containing three WizardStep children.
{
  "version": "1",
  "form": {
    "name": "Registration",
    "type": "form",
    "props": {},
    "components": [
      {
        "name": "wizard",
        "type": "Wizard",
        "props": {
          "validateOnNext": true,
          "validateOnFinish": true,
          "showSteps": true,
          "showStepsLabels": true,
          "nextButtonLabel": "Next →",
          "previousButtonLabel": "← Back",
          "finishButtonLabel": "Create Account"
        },
        "components": [
          {
            "name": "step1",
            "type": "WizardStep",
            "props": { "label": "Personal Info" },
            "components": [
              {
                "name": "firstName",
                "type": "Input",
                "props": {
                  "label": "First name",
                  "required": true
                },
                "validations": [
                  { "type": "required", "message": "First name is required" }
                ]
              },
              {
                "name": "lastName",
                "type": "Input",
                "props": {
                  "label": "Last name",
                  "required": true
                },
                "validations": [
                  { "type": "required", "message": "Last name is required" }
                ]
              },
              {
                "name": "accountType",
                "type": "Dropdown",
                "props": {
                  "label": "Account type",
                  "required": true,
                  "data": [
                    { "label": "Personal", "value": "personal" },
                    { "label": "Business", "value": "business" }
                  ]
                },
                "validations": [
                  { "type": "required", "message": "Select an account type" }
                ]
              },
              {
                "name": "companyName",
                "type": "Input",
                "props": {
                  "label": "Company name",
                  "required": true
                },
                "renderWhen": "form.data.accountType === 'business'",
                "validations": [
                  { "type": "required", "message": "Company name is required for business accounts" }
                ]
              }
            ]
          },
          {
            "name": "step2",
            "type": "WizardStep",
            "props": { "label": "Account Setup" },
            "components": [
              {
                "name": "email",
                "type": "Input",
                "props": {
                  "label": "Email address",
                  "required": true
                },
                "validations": [
                  { "type": "required", "message": "Email is required" },
                  { "type": "email", "message": "Enter a valid email address" }
                ]
              },
              {
                "name": "password",
                "type": "Input",
                "props": {
                  "label": "Password",
                  "type": "password",
                  "required": true
                },
                "validations": [
                  { "type": "required", "message": "Password is required" },
                  { "type": "minLength", "value": 8, "message": "Password must be at least 8 characters" }
                ]
              }
            ]
          },
          {
            "name": "step3",
            "type": "WizardStep",
            "props": { "label": "Confirm" },
            "components": [
              {
                "name": "confirmHeader",
                "type": "Header",
                "props": {
                  "text": "Review your details before creating your account."
                }
              },
              {
                "name": "termsCheckbox",
                "type": "Checkbox",
                "props": {
                  "label": "I agree to the Terms of Service",
                  "required": true
                },
                "validations": [
                  { "type": "required", "message": "You must accept the terms to continue" }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
}
Key things in this schema:
  • validateOnNext: true — the “Next” button won’t advance until the current step’s required fields pass
  • validateOnFinish: true — the “Create Account” button re-validates all steps before firing the finish event
  • renderWhen on companyName — the field only appears when accountType === 'business', and its required validation only runs when visible
  • All fields write to the same flat form.data object regardless of which step they’re in

3. Render the wizard


const componentStore = new ComponentStore(rsComponents)

function RegistrationForm() {
  const handleFinish = async (formData: Record<string, unknown>) => {
    // formData contains all fields from all steps
    const response = await fetch('/api/register', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(formData),
    })

    if (response.ok) {
      console.log('Registration complete')
    }
  }

  return (
    <FormViewer
      form={JSON.stringify(schema)}
      componentStore={componentStore}
      onActionEventAsync={async (event) => {
        if (event.name === 'onFinish') {
          await handleFinish(event.args.formData)
        }
      }}
    />
  )
}
The onFinish event fires when the user clicks “Create Account” and all validations pass. event.args.formData contains every field value across all three steps.

4. Access the active step programmatically

If you need to know which step the user is on — for analytics or to drive external UI — read form.data.__wizardActiveIndex in a renderWhen expression, or access it via a form reference:

function RegistrationForm() {
  const formRef = useRef<FormViewerRef>(null)

  const logCurrentStep = () => {
    const data = formRef.current?.getFormData()
    console.log('Current step index:', data?.__wizardActiveIndex)
  }

  return (
    <FormViewer
      ref={formRef}
      form={JSON.stringify(schema)}
      componentStore={componentStore}
    />
  )
}

5. Load the schema from an API

For schemas managed server-side, load them before rendering:

function RegistrationForm() {
  const [schema, setSchema] = useState<string | null>(null)

  useEffect(() => {
    fetch('/api/forms/registration')
      .then(r => r.json())
      .then(data => setSchema(JSON.stringify(data)))
  }, [])

  if (!schema) return <div>Loading...</div>

  return (
    <FormViewer
      form={schema}
      componentStore={componentStore}
    />
  )
}

Validation reference for wizard steps

ScenarioSetting
Block next step until fields passvalidateOnNext: true on Wizard
Re-validate all steps on finishvalidateOnFinish: true on Wizard
Skip validation on a stepvalidateOnNext: false on Wizard (applies globally)
Conditional required fieldUse renderWhen — hidden fields skip validation automatically
Custom validation messageSet message on each validation rule

Troubleshooting

“Next” button advances even with empty required fields — check that validateOnNext is set to true in the Wizard props, not inside a WizardStep. Conditional field validates when hidden — this is a schema issue. If you add required validation to a field with renderWhen, FormEngine skips it automatically when the condition is false. Double-check the condition expression syntax. onFinish not firing — make sure validateOnFinish: true is set, and that all steps actually pass. Any step with a failing required field will block the finish event. RSuite styles not loading — confirm import 'rsuite/dist/rsuite.min.css' is in your app entry point before the component renders.

Next steps

Last modified on April 16, 2026