The quickest way to add a form wizard to a web application is to use a JavaScript form library that already supports multi-step flows. Instead of building page navigation, validation, and conditional logic from scratch, you define the form as JSON and let the rendering library handle the runtime behavior.
SurveyJS is a good fit for this. It lets you model forms as JSON schemas, configure validation and logic declaratively, and render the same form in React, Angular, Vue, or plain JavaScript. Survey Creator allows you to generate that schema visually, and Form Library renders it in your application.
This article shows how to create a multi-step form wizard with SurveyJS and embed it in different JavaScript stacks.
What is a Form Wizard
A form wizard is a form navigation pattern that splits a long or complex form into a sequence of smaller steps. Instead of showing every input at once, the UI presents one section, question, or field at a time and guides the user through the flow with navigation controls and progress indicators. This approach improves usability for long forms such as the sample background check consent form shown below:
Why Use SurveyJS for Multi-Step Forms
SurveyJS separates form design from rendering.
You define the form structure, validation rules, navigation, and logic in JSON. Then the Form Library renders that schema in the framework of your choice. SurveyJS Form Library is framework-specific at the UI layer, while survey-core contains the shared model and runtime logic, so you can reuse the same form definition across React, Angular, Vue, and plain JavaScript.
To learn more about SurveyJS architecture, please refer to the SurveyJS Architecture guide.
Survey Creator is the visual form builder used to generate and edit form schemas. It supports drag-and-drop form design, a JSON editor, validation rules, and conditional logic configuration. The generated JSON can then be rendered by SurveyJS Form Library.
In practice, that gives you:
- visual form authoring
- JSON-based configuration
- built-in validation
- conditional logic
- reusable rendering across frameworks
SurveyJS Rendering Packages
SurveyJS ships framework-specific rendering packages on top of survey-core. Using these packages, you can embed multi-step forms into any
website.
| Framework | NPM Package | Get Started guide |
|---|---|---|
| React | survey-react-ui |
React Get Started Guide |
| Angular | survey-angular-ui |
Angular Get Started Guide |
| Vue.js | survey-vue3-ui |
Vue Get Started Guide |
| Plain JavaScript | survey-js-ui |
Plain JavaScript Get Started Guide |
All of them render the same JSON schema, which means the form definition stays the same even if the frontend stack changes.
Define the Wizard as JSON
In SurveyJS, a form is described as a JSON schema. At the top level, you define survey metadata and runtime options. Inside pages, you define the questions shown in each step.
Here is a simplified example of a background check consent form:
const surveyJson = {
"title": "Background check consent form",
"pages": [
{
"name": "personal-details",
"title": "Personal Details",
"elements": [
{
"type": "text",
"name": "surname",
"title": "Surname",
"maxLength": 35
},
{
"type": "text",
"name": "surname-at-birth",
"startWithNewLine": false,
"title": "Surname at birth",
"maxLength": 35
},
{
"type": "text",
"name": "dob",
"title": "Date of Birth",
"validators": [
{
"type": "expression",
"text": "You must be at least 18 y.o. to submit the form.",
"expression": "age({dob}) >= 18"
}
],
"inputType": "date",
},
{
"type": "radiogroup",
"name": "gender",
"maxWidth": "50%",
"title": "Gender",
"commentText": "Other (describe)",
"choices": [
{
"value": "female",
"text": "Female"
},
{
"value": "male",
"text": "Male"
},
{
"value": "other",
"text": "Other"
}
],
"otherText": "Other (please specify)",
"otherErrorText": "Response required: please specify your gender.",
"colCount": 3
},
]
}
],
"showProgressBar": true,
"progressBarLocation": "belowheader",
"progressBarType": "questions",
"completeText": "Submit Form",
"questionsOnPageMode": "questionPerPage"
}
The questionsOnPageMode is a survey-level setting that controls how the runtime treats steps. It works as the switch that determines whether navigation happens by page, by question, or by input.
The property accepts the following values:
-
singlePage– Combines all survey pages into a single page. -
questionPerPage– Displays each question on a separate page. -
inputPerPage– Displays each input field on a separate page. Complex questions—such as Single-Select Matrix, Multi-Select Matrix, Dynamic Matrix, Dynamic Panel, and Multiple Textboxes—are split so that each input field appears on its own page. -
standard(default) – Retains the original single- or multi-page structure.
questionsOnPageMode: "questionPerPage" turns the form into a question-by-question wizard, regardless of how many elements are on a page. If you want each declared page to behave as a wizard step, use the default page structure instead of question-per-page mode.
Configure Wizard Behavior in Survey Creator
You can define the JSON manually, but it is faster to configure the form visually in Survey Creator.
Survey Creator produces a JSON configuration that Form Library can render later, and the survey-level settings include layout and navigation configuration. To configure a multi-step form in the builder UI:
- At the top of the Property Grid, select Survey to switch to the survey-level settings.
- Under Navigation, locate the Survey layout setting.
- Select the option that suits your use case.
Render the Form Wizard
Once the JSON schema is ready, pass it to a Model from survey-core and render it with the package for your framework.
React
"use client";
import { useMemo } from "react";
import "survey-core/survey-core.css";
import { Model } from "survey-core";
import { Survey } from "survey-react-ui";
const surveyJson = {
/* ... */
};
export default function SurveyWizard() {
const survey = useMemo(() => new Model(surveyJson), []);
return <Survey model={survey} />;
}
Angular
import { Component } from "@angular/core";
import { Model } from "survey-core";
@Component({
selector: "app-root",
template: `<survey [model]="survey"></survey>`
})
export class AppComponent {
survey = new Model(surveyJson);
}
And in your Angular module:
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { SurveyModule } from "survey-angular-ui";
import { AppComponent } from "./app.component";
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, SurveyModule],
bootstrap: [AppComponent]
})
export class AppModule {}
Vue
<script setup lang="ts">
import "survey-core/survey-core.css";
import { Model } from "survey-core";
import { SurveyComponent } from "survey-vue3-ui";
const survey = new Model(surveyJson);
</script>
<template>
<SurveyComponent :model="survey" />
</template>
Plain JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>SurveyJS Form Wizard</title>
<link
rel="stylesheet"
href="https://unpkg.com/survey-core/survey-core.min.css"
/>
<script src="https://unpkg.com/survey-core/survey.core.min.js"></script>
<script src="https://unpkg.com/survey-js-ui/survey-js-ui.min.js"></script>
</head>
<body>
<div id="surveyContainer"></div>
<script>
const surveyJson = {
/* ... */
};
const survey = new Survey.Model(surveyJson);
document.addEventListener("DOMContentLoaded", function () {
survey.render(document.getElementById("surveyContainer"));
});
</script>
</body>
</html>
Final thoughts
A form wizard is not just a UI convenience. It is a runtime model for managing long-form input: sequence, validation, conditional branching, and completion. SurveyJS is useful here because those concerns live in the schema instead of being hand-coded into each frontend implementation.
That design has two practical benefits. First, the form definition becomes portable across frameworks. Second, the logic is easier to review, version, and maintain because it is expressed declaratively in JSON rather than spread across multiple components and event handlers.
If your goal is to ship a multi-step form quickly without losing control over validation and logic, that is a solid architecture.


Top comments (0)