This content originally appeared on Envato Tuts+ Tutorials and was authored by Andy Leverenz
In this tutorial, we will build a multi step form using JavaScript, HTML, and CSS (perfect for questionnaires and quizzes).
Filling out long forms can be tedious! As designers we can enhance the experience by focusing on individual components within a multi step form. This design pattern promotes a more user-friendly way to capture user data while sometimes asking for much of it.
The goal with our multi step form will be to reduce the burden of having a more extended form to capture user data, all while ensuring the appropriate data gets submitted.
Our Multi Step Form Demo
Answer the quiz questions and see how our form makes the whole process much more pleasant! Bear in mind that when the form is submitted, nothing currently happens with the data.
1. Start With the HTML
Our HTML markup will effectively give us a tab-based layout. We’ll have three buttons, a form, and some status indicators so a user will be sure what step they are currently on.
<div class="container"> <div class="tab-status"> <span class="tab active">1</span> <span class="tab">2</span> <span class="tab">3</span> </div> <form action="#"> <div role="tab-list"> <div role="tabpanel" id="color" class="tabpanel"> <h3>What is your favorite color?</h3> <textarea name="color" class="form-input" placeholder="Ruby red"></textarea> </div> <div role="tabpanel" id="hobbies" class="tabpanel hidden"> <h3>What are your hobbies?</h3> <textarea name="hobbies" class="form-input" placeholder="Mountain climbing, Guitar, Skateboarding"></textarea> </div> <div role="tabpanel" id="occupation" class="tabpanel hidden"> <h3>What is your occupation?</h3> <textarea name="occupation" class="form-input" placeholder="Web Designer"></textarea> </div> </div> <div class="pagination"> <a class="btn hidden" id="prev">Previous</a> <a class="btn" id="next">Continue</a> <button class="btn btn-submit hidden" id="submit">Submit</button> </div> </form> </div>
I’ll start with three questions, but you can extend this to include however many you prefer. The JavaScript code, in the end, is dynamic, which means you can easily add and remove additional questions.
2. Styling the Form With CSS
Without CSS, the multi-step approach doesn’t convey as we’d hope. Here’s the CSS I used to style the HTML.
:root { --color-1: #6366f1; --color-1-hover: #4338ca; --color-2: #06b6d4; --color-2-hover: #0891b2; --text-color: #312e81; --status-btn-bg: #f8fafc; --status-btn-bg-hover: #f1f5f9; } body { background: linear-gradient(to left, var(--color-1), var(--color-2)); } .container { margin: 10rem auto; max-width: 500px; background: white; border-radius: 1rem; padding: 2rem; } .form-input { width: 100%; border: 1px solid #ddd; border-radius: .5rem; box-shadow: inset 0px 1px 2px rgba(0, 0, 0, .1); padding: 1rem; box-sizing: border-box; color: var(--text-color); transition: ease-in-out .3s all; } .form-input::placeholder { color: #cbd5e1; } .form-input:focus { outline: none; border-color: var(--color-1); } .btn:focus-within, .form-input:focus-within { box-shadow: #f8fafc 0px 0px 0px 2px, #c7d2fe 0px 0px 0px 6px, #0000 0px 1px 2px 0px; } textarea.form-input { min-height: 150px; } .btn { border: 0; background: var(--color-1); padding: 1rem; border-radius: 25px; color: white; cursor: pointer; } .btn[disabled] { opacity: .5; pointer-events: none; } .btn:hover { background: var(--color-1-hover); transition: ease-in-out .3s all; } .btn-submit { background-color: var(--color-2); } .btn-submit:hover { background-color: var(--color-2-hover); } .pagination { margin-top: 1rem; display: flex; align-items: center; justify-content: center; } .pagination .btn { width: 100%; text-align: center; margin: 0 6px; } .tab-status { display: flex; align-items: center; } .tab-status span { appearance: none; background: var(--status-btn-bg); border: none; border-radius: 50%; width: 2rem; height: 2rem; margin-right: .5rem; display: flex; align-items: center; justify-content: center; } .tab-status span.active { background-color: var(--color-2); color: white; } .hidden { display: none; }
3. Onto the JavaScript
Let’s start by declaring some variables. The first three will target the buttons I mentioned previously. We’ll then target the tab panels and tabs as a collection of elements known as a NodeList in JavaScript. That’s a fancy way of calling it an Array.
I created an isEmpty
function to help quickly determine if a string value of form input is empty or not.
Finally, there’s the currentStep
variable which will change as the Next and Previous buttons are clicked.
const previousButton = document.querySelector('#prev') const nextButton = document.querySelector('#next') const submitButton = document.querySelector('#submit') const tabTargets = document.querySelectorAll('.tab') const tabPanels = document.querySelectorAll('.tabpanel') const isEmpty = (str) => !str.trim().length let currentStep = 0
Next and Previous Buttons
Our Next and Previous buttons will be how the user navigates the questionnaire. We’ll leverage the currentStep
variable to render the appropriate step and active tab dynamically. Because it returns a number value, we can target the NodeList dynamically.
// Next: Change UI relative to the current step and account for button permissions nextButton.addEventListener('click', (event) => { // Prevent default on links event.preventDefault() // Hide current tab tabPanels[currentStep].classList.add('hidden') tabTargets[currentStep].classList.remove('active') // Show next tab tabPanels[currentStep + 1].classList.remove('hidden') tabTargets[currentStep + 1].classList.add('active') currentStep += 1 })
The Next button, once clicked, will signal the HTML/CSS to get busy, hiding the active tab and tab panel and showing the following question in the wizard.
We’ll extend this action to call some more functions. One function will be responsible for updating the status indicators, and the other will validate the user entered a response before they can continue.
Updating Status Dynamically
To update the status, we need to perform a conditional operation that checks the state of the currentStep
variable.
Using the tabTargets
variable, we can determine how many tabs there are with the .length()
method. The length()
method dynamically returns the number of tabs in the HTML.
Below, I added comments in the code to better denote what happens after each conditional statement.
function updateStatusDisplay() { // If on the last step, hide the next button and show submit if (currentStep === tabTargets.length - 1) { nextButton.classList.add('hidden') previousButton.classList.remove('hidden') submitButton.classList.remove('hidden') validateEntry() // If it's the first step, hide the previous button } else if (currentStep == 0) { nextButton.classList.remove('hidden') previousButton.classList.add('hidden') submitButton.classList.add('hidden') // In all other instances, display both buttons } else { nextButton.classList.remove('hidden') previousButton.classList.remove('hidden') submitButton.classList.add('hidden') } }
We’ll dynamically show and hide controls relative to the form wizard’s beginning, middle, and end.
Validating User Input
For a multi-step questionnaire/quiz/form to work correctly, we want to ensure data gets adequately submitted.
This tutorial only scratches the surface of what you could validate on the front end, but for now, we are just checking that a value exists for each question.
To extend this functionality, you might check for additional criteria like a length of an answer, if any spam/code entries might otherwise harm the website and more. I’d also advise adding server-side validation so no harmful code enters any database.
function validateEntry() { // Query for the current panel's Textarea input let input = tabPanels[currentStep].querySelector('.form-input') // Start by disabling the continue and submit buttons nextButton.setAttribute('disabled', true) submitButton.setAttribute('disabled', true) // Validate on initial function fire setButtonPermissions(input) // Validate on input input.addEventListener('input', () => setButtonPermissions(input)) // Validate if blurring from input input.addEventListener('blur', () => setButtonPermissions(input)) } function setButtonPermissions(input) { if (isEmpty(input.value)) { nextButton.setAttribute('disabled', true) submitButton.setAttribute('disabled', true) } else { nextButton.removeAttribute('disabled') submitButton.removeAttribute('disabled') }
I added two functions that work together to help validate that a value is present for each question.
We’ll first validate when the function is initially called and then add a couple of event listener functions to set button permissions dynamically as you interact with the form.
This will disable or enable the Next button and Submit buttons relative to the currentStep
variable value and see if there is text present inside each form field.
We add the two functions to the original nextButton
function and call them after each click.
// Next: Change UI relative to the current step and account for button permissions nextButton.addEventListener('click', (event) => { event.preventDefault() // Hide current tab tabPanels[currentStep].classList.add('hidden') tabTargets[currentStep].classList.remove('active') // Show next tab tabPanels[currentStep + 1].classList.remove('hidden') tabTargets[currentStep + 1].classList.add('active') currentStep += 1 validateEntry() updateStatusDisplay() })
Our previous button resembles the following button logic with slightly different math.
// Previous: Change UI relative to the current step and account for button permissions previousButton.addEventListener('click', (event) => { event.preventDefault() // Hide current tab tabPanels[currentStep].classList.add('hidden') tabTargets[currentStep].classList.remove('active') // Show the previous tab tabPanels[currentStep - 1].classList.remove('hidden') tabTargets[currentStep - 1].classList.add('active') currentStep -= 1 nextButton.removeAttribute('disabled') updateStatusDisplay() })
We needn’t call the validateEntry()
function on the previous button click as it’s assumed there would already be a value in the form field.
Putting it All Together
Below is the final result (check out the JS tab to see all the code together). The JavaScript code could be more optimized for reusability. Still, it is enough context to help you learn how to build a simple form to navigate, and it makes a user's life easier when it comes to focusing on a specific question and answering it simply.
This content originally appeared on Envato Tuts+ Tutorials and was authored by Andy Leverenz
Andy Leverenz | Sciencx (2022-10-03T23:16:01+00:00) How to Build a Multi Step Form Wizard with JavaScript. Retrieved from https://www.scien.cx/2022/10/03/how-to-build-a-multi-step-form-wizard-with-javascript/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.