How to Develop a Multi-Step Form with Progress Bar using Tailwind CSS and ReactJS

  • react-js
  • 62 Views
  • 8 Min Read
  • 9 Jul 2024

Multi-Step Form with Progress bar UI

 

Nowadays, almost all websites and applications have forms where we submit important details and send messages to the app handler. However, creating multi-step forms is considered very effective for user experience when user input has to be handled in many steps, such as in registration processes, surveys, and checkout forms.

 

In this article, we will teach you how to create a multi-step form with a progress bar using Tailwind CSS and ReactJS. We will describe the project step by step in easy language so that you will be able to create this project completely and also understand it.

 

So let's get started.

 

 

Table of contents:

 

  1. Prerequisites
  2. Project Setup with Tailwind CSS Configuration
  3. Starting project
  4. Building logic inside components
  5. Enhancing project

 

 

Prerequisites

 

Before we start developing a multi-step form with a progress bar using Tailwind CSS and ReactJS, ensure you have the following prerequisites:

 

 

 

  • Basic Understanding of Tailwind CSS.

 

  • Node.js and npm Installed.

 

I hope you have all the prerequisites mentioned above. Now, let’s start with the project and see how we can build it step by step.

 

 

Project Setup with Tailwind CSS Configuration

 

First, set up a new React project if you don't have one already:

 

npx create-react-app multi-step-form
cd multi-step-form

 

Once you've used this command, your project is ready. Now, let's organize our folder structure. We'll remove unnecessary files and clean up some code in certain files. While we're removing these files, it doesn't mean they're unnecessary—they have their advantages. However, for this project, we don't need them, so we're removing them. We're organizing everything clearly because it's important in professional development.

 

In the "public" folder, keep only the index.html file and delete the rest. In the "src" folder, keep App.css, App.js, index.css, and index.js, and delete the other files.

 

Now let's delete all the code from the App.css, index.css, index.js, and App.js files and add the following code inside the App.js and index.js files.

 

// src\App.js

import React from 'react'

function App() {
  return (
    <div>App</div>
  )
}

export default App;

 

// src\index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
// 
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <App />
);

 

Now your entire project should look like the image below.

 

Project Structure

 

Great! Our React project is ready. Now we need to install Tailwind CSS and configure it in this project. Let's do this together.

 

Install Tailwind CSS:

 

npm install -D tailwindcss //This command will install Tailwind CSS in your project

 

Initialize Tailwind CSS:

 

npx tailwindcss init

 

After running the above command, it will automatically generate one file named "tailwind.config.js" in your project.

 

Update tailwind.config.js file:

 

In the "tailwind.config.js" file, add the following code.

 

// tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{js,jsx,ts,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
}

 

Update index.css file:

 

In the "index.css" file, include the following code.

 

/* src\index.css */

@tailwind base;
@tailwind components;
@tailwind utilities;

 

Congratulations! Our project setup is ready. Now we can use Tailwind CSS. Let's start building the project!

 

Now, if you create the following files and copy-paste the code, it will work. But you might not fully understand it. I recommend you read carefully and paste the code into your project to understand how it works.

 

 

Starting project

 

For this project, we need to create some folders and files. First, create a "components" folder inside the "src" directory. Then, create two files inside the "components" folder: "Stepper.jsx" and "StepperControl.jsx".

 

Next, create a folder named "steps" inside the "components" folder. Then, inside the "steps" folder, create four files: Account.jsx, Details.jsx, Final.jsx, and Payment.jsx.

 

Next, create another folder inside the "src" directory named "contexts". Inside the "contexts" folder, create one file named "StepperContext.js".

 

Now your project folder structure should look like this:

 

Folder Structure

 

 

Building logic inside components

 

In the StepperControl.jsx file, we will create "Back" and "Next" buttons that handle navigation between steps when clicked.

 

// src\components\StepperControl.jsx

function StepperControl({ handleClick, currentStep, steps }) {
    return (
        <div className='container flex justify-around mt-4 mb-8'>

            {/* back button */}
            <button
                onClick={() => handleClick()}
                className={`bg-white text-slate-400 uppercase py-2 px-4 rounded-xl font-semibold border-2 border-slate-300 hover:bg-slate-700 hover:text-white transition duration-200 ease-in-out ${currentStep === 1 ? "opacity-50 cursor-not-allowed" : " "}`}>Back</button>

            {/* Next button */}
            <button
                onClick={() => handleClick("next")}
                className='bg-green-500 text-white uppercase py-2 px-4 rounded-xl font-semibold cursor-pointer  hover:bg-slate-700 hover:text-white transition duration-200 ease-in-out'>{currentStep === steps.length - 1 ? "Confirm" : "Next"}</button>

        </div>
    )
}

export default StepperControl


In the StepperControl component, the "Back" button is disabled on the first step, and the "Next" button changes to "Confirm" on the last step. These buttons use the handleClick function to move between steps.

 

In the Stepper.jsx file we will create a component that displays the steps of a multi-step form.

 

// src/components/Stepper.jsx

import { useEffect, useRef, useState } from 'react';

function Stepper({ steps, currentStep }) {
  const [newStep, setNewStep] = useState([]);
  const stepRef = useRef();

  useEffect(() => {
    // Function to update steps based on the current step number
    const updateStep = (stepNumber, steps) => {
      const newSteps = [...steps];
      let count = 0;

      while (count < newSteps.length) {
        // Current step
        if (count === stepNumber) {
          newSteps[count] = {
            ...newSteps[count],
            highlighted: true,
            selected: true,
            completed: true,
          };
          count++;
        }
        // Step completed
        else if (count < stepNumber) {
          newSteps[count] = {
            ...newSteps[count],
            highlighted: false,
            selected: true,
            completed: true,
          };
          count++;
        }
        // Step pending
        else {
          newSteps[count] = {
            ...newSteps[count],
            highlighted: false,
            selected: false,
            completed: false,
          };
          count++;
        }
      }
      return newSteps;
    };

    // Create initial steps state
    const stepsState = steps.map((step, index) =>
      Object.assign(
        {},
        {
          description: step,
          completed: false,
          highlighted: index === 0 ? true : false,
          selected: index === 0 ? true : false,
        }
      )
    );
    stepRef.current = stepsState;
    const current = updateStep(currentStep - 1, stepRef.current);
    setNewStep(current);
  }, [steps, currentStep]);

  // Function to display each step
  const displaySteps = newStep.map((step, index) => {
    return (
      <div
        key={index}
        className={index !== newStep.length - 1 ? 'w-full flex items-center' : 'flex items-center'}>
        <div className="relative flex flex-col items-center text-teal-600">
          <div className={`rounded-full transition duration-500 ease-in-out border-2 border-gray-300 h-12 w-12 flex items-center justify-center py-3 ${step.selected ? "bg-green-600 text-white font-bold border border-green-600" : " "}`}>
            {/* Display step number or checkmark if completed */}
            {step.completed ? (
              <span className='text-white font-bold text-xl'>&#10003;</span>
            ) : (index + 1)}
          </div>

          <div className={`absolute top-0 text-center mt-16 w-50 text-xs font-medium uppercase ${step.highlighted ? "text-gray-900" : "text-gray-400"}`}>
            {/* Display step description */}
            {step.description}
          </div>
        </div>
        <div className={`flex-auto border-t-2 transition duration-500 ease-in-out ${step.completed ? "border-green-600" : "border-gray-300"}`}></div>
      </div>
    );
  });

  return (
    <div className="mx-4 p-4 flex justify-between items-center">
      {displaySteps}
    </div>
  );
}

export default Stepper;


In the Stepper.jsx file, we've created a Stepper component to manage and display the progress of steps in a multi-step form. This component dynamically updates each step's appearance based on the current step and overall progress. Using state management, it highlights the current step, shows completed steps with a checkmark, and adjusts the visual state of pending steps. By initializing the steps and determining the active step using currentStep, the component dynamically renders either step numbers or checkmarks, ensuring a clear and intuitive user experience.

 

In the StepperContext.js file, we will create a context to manage and share user data across multiple steps in a multi-step form.

 

// src/contexts/StepperContext.js

import { createContext, useState } from 'react';

export const StepperContext = createContext();

export const StepperProvider = ({ children }) => {
  const [userData, setUserData] = useState({});

  return (
    <StepperContext.Provider value={{ userData, setUserData }}>
      {children}
    </StepperContext.Provider>
  );
};

 

In StepperContext.js, we've created a context using React's createContext. This context manages user data for our multi-step form. We set up a StepperProvider component that uses useState to keep track of userData, which stores the form data, and setUserData, which updates this data. StepperProvider wraps its children with StepperContext.Provider. This allows any component within the context to easily access and modify the user data.

 

In the Account.jsx file, we create a form for username and password, updating the userData state through the handleChange function.

 

// src/components/steps/Account.jsx

import { useContext } from 'react';
import { StepperContext } from '../../contexts/StepperContext';

function Account() {
    const { userData, setUserData } = useContext(StepperContext);

    const handleChange = (e) => {
        const { name, value } = e.target;
        setUserData({ ...userData, [name]: value });
    };

    return (
        <div className='flex flex-col'>
            <div className='w-full mx-2'>
                <div className='font-bold h-4 text-gray-500 text-xs'>
                    USERNAME
                </div>
                <div className='my-2 p-1 border border-gray-200 rounded'>
                    <input
                        onChange={handleChange}
                        value={userData["username"] || ""}
                        name='username'
                        placeholder='username'
                        className='p-1 px-2 outline-none w-full text-gray-800'
                    />
                </div>
            </div>

            <div className='w-full mx-2'>
                <div className='font-bold h-4 mt-3 text-gray-500 text-xs'>
                    PASSWORD
                </div>
                <div className='my-2 p-1 border border-gray-200 rounded'>
                    <input
                        onChange={handleChange}
                        value={userData["password"] || ""}
                        name='password'
                        placeholder='password'
                        type='password'
                        className='p-1 px-2 outline-none w-full text-gray-800'
                    />
                </div>
            </div>
        </div>
    );
}

export default Account;

 

In Account.jsx, we created a React component that utilizes useContext to access userData and setUserData from the StepperContext. This component manages user input for username and password fields within a multi-step form. The handleChange function updates userData based on user input, ensuring seamless data management across the form's steps.

 

In the Details.jsx file, we create a form for address and city, updating the userData state through the handleChange function.

 

// src/components/steps/Details.jsx

import { useContext } from 'react';
import { StepperContext } from '../../contexts/StepperContext';

function Details() {
  const { userData, setUserData } = useContext(StepperContext);

  // Function to handle input changes and update userData state
  const handleChange = (e) => {
    const { name, value } = e.target;
    setUserData({ ...userData, [name]: value });
  };

  return (
    <div className='flex flex-col'>
      {/* Address input */}
      <div className='w-full mx-2'>
        <div className='font-bold h-4 text-gray-500 text-xs'>
          ADDRESS
        </div>
        <div className='my-2 p-1 border border-gray-200 rounded'>
          <input
            onChange={handleChange}
            value={userData["address"] || ""}
            name='address'
            placeholder='Address'
            className='p-1 px-2 outline-none w-full text-gray-800'
          />
        </div>
      </div>

      {/* City input */}
      <div className='w-full mx-2'>
        <div className='font-bold h-4 mt-3 text-gray-500 text-xs'>
          CITY
        </div>
        <div className='my-2 p-1 border border-gray-200 rounded'>
          <input
            onChange={handleChange}
            value={userData["city"] || ""}
            name='city'
            placeholder='City'
            className='p-1 px-2 outline-none w-full text-gray-800'
          />
        </div>
      </div>
    </div>
  );
}

export default Details;

 

In Details.jsx, we created a component that uses useContext to access userData and setUserData from StepperContext. This component handles user input for the address and city fields. When users type in these fields, the handleChange function updates the userData state in real-time, ensuring the data stays consistent throughout the form.

 

In the Payment.jsx file, we create a form for credit card and expiration date, updating the userData state through the handleChange function.

 

// src/components/steps/Payment.jsx

import { useContext } from 'react';
import { StepperContext } from '../../contexts/StepperContext';

function Payment() {
  const { userData, setUserData } = useContext(StepperContext);

  // Function to handle input changes and update userData state
  const handleChange = (e) => {
    const { name, value } = e.target;
    setUserData({ ...userData, [name]: value });
  };

  return (
    <div className='flex flex-col'>
      {/* Credit Card input */}
      <div className='w-full mx-2'>
        <div className='font-bold h-4 text-gray-500 text-xs'>
          CREDIT CARD
        </div>
        <div className='my-2 p-1 border border-gray-200 rounded'>
          <input
            onChange={handleChange}
            value={userData["creditCard"] || ""}
            name='creditCard'
            placeholder='1234 5678 9012 3456'
            className='p-1 px-2 outline-none w-full text-gray-800'
          />
        </div>
      </div>

      {/* Expiry Date input */}
      <div className='w-full mx-2'>
        <div className='font-bold h-4 mt-3 text-gray-500 text-xs'>
          EXP
        </div>
        <div className='my-2 p-1 border border-gray-200 rounded'>
          <input
            onChange={handleChange}
            value={userData["exp"] || ""}
            name='exp'
            placeholder='YY/MM/DD'
            className='p-1 px-2 outline-none w-full text-gray-800'
          />
        </div>
      </div>
    </div>
  );
}

export default Payment;

 

In Payment.jsx, we created a component using useContext to access userData and setUserData from StepperContext. This component handles user input for credit card and expiry date fields. The handleChange function updates the userData state as users type, keeping the payment details consistent across the form.

 

In Final.jsx file, we'll create a React component to display a congratulatory message and an icon when the multi-step form is completed. We will include an SVG icon, a message confirming account creation, and a button to close the form and go back to the homepage.

 

// src/components/steps/Final.jsx

function Final() {
    return (
      <div className="flex flex-col items-center">
        <div>
          <svg
            className='w-24' fill='#16a34a' viewBox='0 0 20 20' xmlns="http://www.w3.org/2000/svg">
            <path
              fillRule='evenodd'
              d='M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z'
            />
          </svg>
        </div>
        <div className="mt-5 text-xl font-semibold text-green-500"> 🎉 CONGRATULATIONS! 🎉</div>
        <div className="text-lg font-semibold text-gray-400"> Your Account has been created. </div>
        <a className='mt-10' href="/">
          <button className='h-10 px-10 text-green-700 transition-colors duration-150 border border-gray-300 rounded-lg hover:bg-green-500 hover:text-white'>Close</button>
        </a>
      </div>
    );
  }
  
  export default Final;

 

In Final.jsx, we created a React component to celebrate finishing a multi-step form. This component includes an SVG icon, a text message congratulating the user, and another message confirming their account creation. We've added a close button that automatically redirects users to the homepage when clicked.

 

In the App.js file, we'll create the main component for a multi-step form. It manages the current step and user data, allowing smooth navigation between form steps.

 

// src\App.js

import { useState } from 'react';
import { StepperContext } from './contexts/StepperContext';
import Stepper from './components/Stepper';
import StepperControl from './components/StepperControl';
import Account from './components/steps/Account';
import Details from './components/steps/Details';
import Final from './components/steps/Final';
import Payment from './components/steps/Payment';

function App() {
  // State to keep track of the current step
  const [currentStep, setCurrentStep] = useState(1);
  // State to hold user data collected from the form steps
  const [userData, setUserData] = useState('');
  // State to hold final data after completion
  const [finalData, setFinalData] = useState([]);

  // Steps for the multi-step form
  const steps = ['Account information', 'Personal Details', 'Payment', 'Complete'];

  // Function to display the current step component
  const displayStep = (step) => {
    switch (step) {
      case 1:
        return <Account />;
      case 2:
        return <Details />;
      case 3:
        return <Payment />;
      case 4:
        return <Final />;
      default:
        return null;
    }
  };

  // Function to handle navigation between steps
  const handleClick = (direction) => {
    let newStep = currentStep;
    direction === 'next' ? newStep++ : newStep--;
    // Check if steps are within bounds
    if (newStep > 0 && newStep <= steps.length) {
      setCurrentStep(newStep);
    }
  };

  return (
    <div className="md:w-1/2 mx-auto shadow-xl rounded-2xl pb-2 bg-white">

      <div className="container horizontal mt-5">
        <Stepper steps={steps} currentStep={currentStep} />

        <div className='my-10 p-10'>
          <StepperContext.Provider value={{ userData, setUserData, finalData, setFinalData }}>
            {displayStep(currentStep)}
          </StepperContext.Provider>
        </div>
      </div>

      {currentStep !== steps.length && (
        <StepperControl
          handleClick={handleClick}
          currentStep={currentStep}
          steps={steps}
        />
      )}
    </div>
  );
}

export default App;

 

In App.js, we've built the main component for our multi-step form. Using useState, we handle the currentStep, userData, and finalData. The steps array defines each form step. Based on the current step, the displayStep function selects and displays the corresponding component (Account, Details, Payment, or Final). Navigation between steps is managed by handleClick, which adjusts the currentStep state. The StepperContext.Provider wraps the step components, providing access to user and final data context. Additionally, Stepper and StepperControl components aid in step navigation and control.

 

Congratulations! If you have followed this article and the code correctly, you should have successfully created this project without any errors, and your UI should resemble the following GIF.

 

Multi-Step Form with Progress bar

 

 

Enhancing project

 

You can make this project more attractive which will enhance the user experience. For example, if a user tries to go to the next step without filling in the required fields, show them an error message to prevent them from moving forward without entering the needed information.

 

You can also add real-time feedback where the user can get an instant verification message as soon as they type any particular information.

 

Another enhancement can be saving the form state so users can resume their progress later if they navigate away from the page. Also if you have multiple forms and some sections are optional, you can add a skip button feature, allowing users to skip those parts and complete the form submission more efficiently.

 

 

Conclusion

 

We quickly learned how to create a multi-step form with a progress bar in React JS using Tailwind CSS. In an era where data is king, this forms a potent method of collecting it. We have seen how in the front-end part we can create good UX by clear navigation, and ensure that the process is smoothly transitioning from one step to another, it will not only give a better user experience but also make the entire project more efficient. It does not matter if you want to create a designed registration form or submit data on thousands of different interactions, these best practices are probably going to make your web developer's career easier.

 

If you have any questions about this article or related to web development, you can ask them in the question box given below, and you will get the answer soon.

Didn't find your answer? Add your question.

Share

Comments (0)

No comments yet. Be the first to comment!

About Author

Username

Poojan Joshi ( poojanjoshi )

Full Stack JavaScript Developer

Joined On 12 Apr 2024

More from Poojan Joshi

Top 60 Eye-Catching Button Designs Users Can’t Wait to Click - With Source Code

ui-ux

11 Oct 2024

Discover 60 eye-catching button designs with source code, crafted with HTML and CSS to enh....

How to Check Website Speed: Key Metrics, Tools, and Optimization Tips

web-development

21 Sep 2024

Learn how to test your website speed, key metrics to track, top tools to use, and expert o....

Everything About Debouncing Function in JavaScript: Comprehensive Guide

javascript

13 Aug 2024

Learn everything about the debouncing function such as what it is, why and where to use it....

How to Create Advanced Search Bar with Suggestions in React: A Step-by-Step Guide

react-js

2 Aug 2024

Learn to build an advanced React search bar with suggestions, highlighted text, efficient ....

How to Create a Full-Featured Todo List App Using ReactJS

react-js

19 Jul 2024

Learn how to build a powerful Todo List App using ReactJS. A step-by-step guide covering d....

How to Create a Full-Featured Todo List App Using HTML, CSS, and JavaScript

javascript

16 Jul 2024

Learn to build a feature-rich to-do list with HTML, CSS, and JavaScript. Perfect for inter....

Popular Posts from Code Mafias

10 Fun Websites for Stress Relief and Relaxation During Coding Breaks

programming

11 Nov 2024

Top 10 fun websites for coders to relax during breaks. Recharge with interactive games, ar....

Mastering HTML: Top 12 Unique HTML Tags with Examples

html

4 May 2024

Through this article, learn those essential HTML tags that many developers do not know abo....

Top 60 Eye-Catching Button Designs Users Can’t Wait to Click - With Source Code

ui-ux

11 Oct 2024

Discover 60 eye-catching button designs with source code, crafted with HTML and CSS to enh....

How to Upload Code to GitHub: Quick Steps and Detailed Instructions for Beginners

github

16 Sep 2024

In order to upload (push) your project to GitHub, it involves multiple steps that need to ....

How to install MongoDB on windows in 2024

mongodb

2 May 2024

MongoDB is a Database Management System based on NoSQL (Not Only SQL) and utilizes JSON-li....

How to Implement Undo Functionality for Deleting Items in Your React App

react-js

28 Sep 2024

Learn how to implement undo functionality for deleting items in React. Follow a step-by-st....