DEV Community

CodeRipple
CodeRipple

Posted on

How to Build a Multi Step Registration Form with HTML, CSS & JavaScript

A multi-step form is a useful way to break a long registration process into smaller, easier-to-complete sections.

In this tutorial, we'll build a Multi Step Registration Form using HTML, CSS and JavaScript. The form includes account details, profile information, a review screen, client-side validation, password strength feedback and a final success state.

What We're Building

Our registration form is divided into three steps:

  1. Account Details — Email, password and password confirmation
  2. Profile — First name, last name, username, role and bio
  3. Review — Review the entered information before creating the account

The component also includes:

  • Step-by-step navigation
  • Responsive layout
  • Email validation
  • Password confirmation
  • Password strength indicator
  • Show/hide password control
  • Username validation
  • Character counter for the bio
  • Editable review screen
  • Terms acceptance validation
  • Success state
  • Start Again option

HTML Structure

The component starts with a main wrapper and contains the header, progress indicator, registration form and success screen.

<div class="cr-step-form" id="crStepForm">

  <div class="cr-step-form__card">

    <div class="cr-step-form__header">

      <span class="cr-step-form__eyebrow">
        CREATE YOUR ACCOUNT
      </span>

      <h2>Join CodeRipple</h2>

      <p>
        Complete the steps below to set up your profile.
      </p>

    </div>


    <div class="cr-step-form__progress">

      <div
        class="cr-step cr-step--active"
        data-step-indicator="1">

        <div class="cr-step__circle">
          <span>1</span>
        </div>

        <div class="cr-step__text">
          <strong>Account</strong>
          <small>Login details</small>
        </div>

      </div>


      <div class="cr-step__line"></div>


      <div
        class="cr-step"
        data-step-indicator="2">

        <div class="cr-step__circle">
          <span>2</span>
        </div>

        <div class="cr-step__text">
          <strong>Profile</strong>
          <small>About you</small>
        </div>

      </div>


      <div class="cr-step__line"></div>


      <div
        class="cr-step"
        data-step-indicator="3">

        <div class="cr-step__circle">
          <span>3</span>
        </div>

        <div class="cr-step__text">
          <strong>Review</strong>
          <small>Confirm details</small>
        </div>

      </div>

    </div>


    <form
      class="cr-step-form__form"
      novalidate>


      <!-- STEP 1 -->

      <section
        class="cr-form-step cr-form-step--active"
        data-step="1">

        <div class="cr-form-step__heading">

          <span class="cr-form-step__number">
            01
          </span>

          <div>
            <h3>Account Details</h3>

            <p>
              Choose the credentials you'll use to sign in.
            </p>
          </div>

        </div>


        <div class="cr-field">

          <label for="crEmail">
            Email Address
          </label>

          <input
            type="email"
            id="crEmail"
            placeholder="you@example.com"
            autocomplete="email">

          <span class="cr-field__error"></span>

        </div>


        <div class="cr-field">

          <label for="crPassword">
            Password
          </label>

          <div class="cr-password-wrap">

            <input
              type="password"
              id="crPassword"
              placeholder="Create a password"
              minlength="8"
              autocomplete="new-password">

            <button
              type="button"
              class="cr-password-toggle"
              aria-label="Show password">
              Show
            </button>

          </div>

          <div class="cr-password-strength">

            <div class="cr-password-strength__bars">
              <span></span>
              <span></span>
              <span></span>
              <span></span>
            </div>

            <span class="cr-password-status">
              Password strength
            </span>

          </div>

          <span class="cr-field__error"></span>

        </div>


        <div class="cr-field">

          <label for="crConfirmPassword">
            Confirm Password
          </label>

          <input
            type="password"
            id="crConfirmPassword"
            placeholder="Enter password again"
            autocomplete="new-password">

          <span class="cr-field__error"></span>

        </div>

      </section>


      <!-- STEP 2 -->

      <section
        class="cr-form-step"
        data-step="2">

        <div class="cr-form-step__heading">

          <span class="cr-form-step__number">
            02
          </span>

          <div>
            <h3>Your Profile</h3>

            <p>
              Tell us a little more about yourself.
            </p>
          </div>

        </div>


        <div class="cr-field">

          <label for="crFirstName">
            First Name
          </label>

          <input
            type="text"
            id="crFirstName"
            placeholder="First name"
            autocomplete="given-name">

          <span class="cr-field__error"></span>

        </div>


        <div class="cr-field">

          <label for="crLastName">
            Last Name
          </label>

          <input
            type="text"
            id="crLastName"
            placeholder="Last name"
            autocomplete="family-name">

          <span class="cr-field__error"></span>

        </div>


        <div class="cr-field">

          <label for="crUsername">
            Username
          </label>

          <input
            type="text"
            id="crUsername"
            placeholder="coderipple_user"
            minlength="3"
            autocomplete="username">

          <small>
            Letters, numbers and underscores only.
          </small>

          <span class="cr-field__error"></span>

        </div>


        <div class="cr-field">

          <label for="crRole">
            Role
          </label>

          <select id="crRole">

            <option value="">
              Select your role
            </option>

            <option value="developer">
              Developer
            </option>

            <option value="designer">
              Designer
            </option>

            <option value="student">
              Student
            </option>

            <option value="freelancer">
              Freelancer
            </option>

            <option value="creator">
              Content Creator
            </option>

            <option value="other">
              Other
            </option>

          </select>

          <span class="cr-field__error"></span>

        </div>


        <div class="cr-field">

          <label for="crBio">
            Short Bio
            <span>Optional</span>
          </label>

          <textarea
            id="crBio"
            maxlength="160"
            placeholder="Tell us a little about yourself..."></textarea>

          <div class="cr-field__counter">
            <span id="crBioCount">0</span>/160
          </div>

        </div>

      </section>


      <!-- STEP 3 -->

      <section
        class="cr-form-step"
        data-step="3">

        <div class="cr-form-step__heading">

          <span class="cr-form-step__number">
            03
          </span>

          <div>
            <h3>Review & Submit</h3>

            <p>
              Check your information before creating your account.
            </p>
          </div>

        </div>


        <div class="cr-review">

          <div class="cr-review__section">

            <div class="cr-review__header">

              <h4>Account</h4>

              <button
                type="button"
                data-edit-step="1">
                Edit
              </button>

            </div>

            <div class="cr-review__row">

              <span>Email Address</span>

              <strong data-review="email">
                Not provided
              </strong>

            </div>

            <div class="cr-review__row">

              <span>Password</span>

              <strong data-review="password">
                ••••••••
              </strong>

            </div>

          </div>


          <div class="cr-review__section">

            <div class="cr-review__header">

              <h4>Profile</h4>

              <button
                type="button"
                data-edit-step="2">
                Edit
              </button>

            </div>

            <div class="cr-review__row">

              <span>Full Name</span>

              <strong data-review="name">
                Not provided
              </strong>

            </div>

            <div class="cr-review__row">

              <span>Username</span>

              <strong data-review="username">
                Not provided
              </strong>

            </div>

            <div class="cr-review__row">

              <span>Role</span>

              <strong data-review="role">
                Not provided
              </strong>

            </div>

            <div class="cr-review__row">

              <span>Bio</span>

              <strong data-review="bio">
                Not provided
              </strong>

            </div>

          </div>

        </div>


        <label class="cr-terms">

          <input
            type="checkbox"
            id="crTerms">

          <span>
            I agree to the
            <a href="#">Terms of Service</a>
            and
            <a href="#">Privacy Policy</a>.
          </span>

        </label>

        <span class="cr-terms-error"></span>

      </section>


      <!-- ACTIONS -->

      <div class="cr-step-form__actions">

        <button
          type="button"
          class="cr-btn cr-btn--back"
          id="crBackBtn"
          disabled>
          ← Back
        </button>

        <div class="cr-step-form__counter">
          Step
          <span id="crCurrentStep">1</span>
          of 3
        </div>

        <button
          type="button"
          class="cr-btn cr-btn--next"
          id="crNextBtn">
          Next Step →
        </button>

        <button
          type="submit"
          class="cr-btn cr-btn--submit"
          id="crSubmitBtn"
          hidden>
          Create Account
        </button>

      </div>

    </form>


    <!-- SUCCESS -->

    <div
      class="cr-success"
      hidden>

      <span class="cr-success__eyebrow">
        ALL DONE
      </span>

      <h3>
        Account Created!
      </h3>

      <p>
        Your demo registration has been completed successfully.
      </p>

      <button
        type="button"
        class="cr-btn cr-btn--restart">
        Start Again
      </button>

    </div>

  </div>

</div>
Enter fullscreen mode Exit fullscreen mode

Styling the Multi Step Form

The component uses a dark interface with purple and cyan accents.

CSS variables make the main colors easy to customize:

.cr-step-form {
  --cr-bg: #070a14;
  --cr-panel: #101426;
  --cr-field: #080c19;
  --cr-text: #ffffff;
  --cr-muted: #8f9bb3;
  --cr-purple: #8658ff;
  --cr-cyan: #28d7ff;
  --cr-green: #3de3a0;
  --cr-danger: #ff6680;

  width: 100%;
  font-family: Inter, system-ui, sans-serif;
}
Enter fullscreen mode Exit fullscreen mode

The main registration card can then use the dark theme:

.cr-step-form__card {
  position: relative;
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  border-radius: 24px;
  overflow: hidden;
  color: var(--cr-text);
}
Enter fullscreen mode Exit fullscreen mode

Only the currently active form section needs to be displayed:

.cr-form-step {
  display: none;
}

.cr-form-step--active {
  display: block;
  animation: crStepEnter 0.35s ease;
}

@keyframes crStepEnter {

  from {
    opacity: 0;
    transform: translateY(8px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }

}
Enter fullscreen mode Exit fullscreen mode

The form can also provide visual feedback for valid and invalid fields:

.cr-field--error input,
.cr-field--error select,
.cr-field--error textarea {
  border-color: var(--cr-danger);
}

.cr-field--valid input,
.cr-field--valid select,
.cr-field--valid textarea {
  border-color: var(--cr-green);
}

.cr-field__error {
  color: var(--cr-danger);
  font-size: 12px;
}
Enter fullscreen mode Exit fullscreen mode

For smaller screens, the multi-column form can switch to a single-column layout:

@media (max-width: 650px) {

  .cr-step-form {
    padding: 16px;
  }

  .cr-form-step {
    width: 100%;
  }

  .cr-step-form__actions {
    grid-template-columns: 1fr 1fr;
  }

}
Enter fullscreen mode Exit fullscreen mode

The full CodeRipple version contains the complete responsive styling, progress states, review cards, form fields, buttons, password meter and success screen.


JavaScript Step Navigation

JavaScript keeps track of the current form step:

let currentStep = 1;
Enter fullscreen mode Exit fullscreen mode

The showStep() function controls which section is visible:

const showStep = (stepNumber) => {

  currentStep = Math.max(
    1,
    Math.min(stepNumber, 3)
  );

  formSteps.forEach((step) => {

    const number =
      Number(step.dataset.step);

    step.classList.toggle(
      "cr-form-step--active",
      number === currentStep
    );

  });

  backBtn.disabled =
    currentStep === 1;

  if (currentStep === 3) {

    updateReview();

    nextBtn.hidden = true;
    submitBtn.hidden = false;

  } else {

    nextBtn.hidden = false;
    submitBtn.hidden = true;

  }

  currentStepText.textContent =
    String(currentStep);

  updateProgress();

};
Enter fullscreen mode Exit fullscreen mode

This keeps the navigation between steps synchronized with the progress indicator.


Validating Step One

The first step checks the email, password and password confirmation before allowing the user to continue.

const validateStepOne = () => {

  const emailValid =
    validateEmail();

  const passwordValid =
    validatePassword();

  const confirmValid =
    validateConfirmPassword();

  return (
    emailValid &&
    passwordValid &&
    confirmValid
  );

};
Enter fullscreen mode Exit fullscreen mode

If any field is invalid, the form remains on the current step and displays the relevant error.


Email Validation

The email field first checks whether a value has been entered.

It then checks the basic email format:

const validateEmail = () => {

  const value =
    email.value.trim();

  if (!value) {

    showError(
      email,
      "Email address is required."
    );

    return false;

  }

  const emailPattern =
    /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;

  if (!emailPattern.test(value)) {

    showError(
      email,
      "Enter a valid email address."
    );

    return false;

  }

  showValid(email);

  return true;

};
Enter fullscreen mode Exit fullscreen mode

This is client-side validation for the demo interface.

A production registration system should also validate and sanitize submitted data on the server.


Password Strength Indicator

The password strength meter checks four conditions:

  • At least 8 characters
  • At least one uppercase letter
  • At least one number
  • At least one special character
const getPasswordStrength = (value) => {

  if (!value) return 0;

  let score = 0;

  if (value.length >= 8) {
    score++;
  }

  if (/[A-Z]/.test(value)) {
    score++;
  }

  if (/[0-9]/.test(value)) {
    score++;
  }

  if (/[^A-Za-z0-9]/.test(value)) {
    score++;
  }

  return score;

};
Enter fullscreen mode Exit fullscreen mode

The interface can display the result as:

  • Very weak
  • Weak
  • Fair
  • Good
  • Strong

The strength indicator updates while the user types.


Confirm Password Validation

The confirmation field checks whether the user entered a value and whether both passwords match:

const validateConfirmPassword = () => {

  const value =
    confirmPassword.value;

  if (!value) {

    showError(
      confirmPassword,
      "Please confirm your password."
    );

    return false;

  }

  if (value !== password.value) {

    showError(
      confirmPassword,
      "Passwords do not match."
    );

    return false;

  }

  showValid(confirmPassword);

  return true;

};
Enter fullscreen mode Exit fullscreen mode

The confirmation is also revalidated when the original password changes.


Profile Validation

Step two validates the profile information.

const validateStepTwo = () => {

  const firstValid =
    validateName(
      firstName,
      "First name"
    );

  const lastValid =
    validateName(
      lastName,
      "Last name"
    );

  const usernameValid =
    validateUsername();

  const roleValid =
    validateRole();

  return (
    firstValid &&
    lastValid &&
    usernameValid &&
    roleValid
  );

};
Enter fullscreen mode Exit fullscreen mode

The bio remains optional.


Username Validation

The username requires at least three characters.

It also accepts only letters, numbers and underscores:

const usernamePattern =
  /^[A-Za-z0-9_]+$/;
Enter fullscreen mode Exit fullscreen mode

If another character is entered, an error message is displayed.


Bio Character Counter

The profile bio has a maximum length of 160 characters.

The counter updates while the user types:

bio.addEventListener(
  "input",
  () => {

    bioCount.textContent =
      String(
        bio.value.length
      );

  }
);
Enter fullscreen mode Exit fullscreen mode

This gives the user immediate feedback without waiting for form submission.


Next and Back Buttons

The Next Step button validates the current section before moving forward.

nextBtn.addEventListener(
  "click",
  (event) => {

    event.preventDefault();

    if (currentStep === 1) {

      if (!validateStepOne()) {
        return;
      }

      showStep(2);

      return;
    }

    if (currentStep === 2) {

      if (!validateStepTwo()) {
        return;
      }

      updateReview();

      showStep(3);

    }

  }
);
Enter fullscreen mode Exit fullscreen mode

The Back button returns to the previous step:

backBtn.addEventListener(
  "click",
  (event) => {

    event.preventDefault();

    if (currentStep > 1) {

      showStep(
        currentStep - 1
      );

    }

  }
);
Enter fullscreen mode Exit fullscreen mode

Building the Review Screen

Before creating the account, the third step displays the information entered in the previous sections.

A helper function updates each review value:

const setReviewValue = (name, value) => {

  const target =
    component.querySelector(
      `[data-review="${name}"]`
    );

  if (!target) return;

  const cleanValue =
    String(value || "").trim();

  target.textContent =
    cleanValue || "Not provided";

};
Enter fullscreen mode Exit fullscreen mode

The full name is created from the first and last name:

const fullName = [
  firstName.value.trim(),
  lastName.value.trim()
]
  .filter(Boolean)
  .join(" ");
Enter fullscreen mode Exit fullscreen mode

The username is displayed with an @ prefix:

const usernameValue =
  username.value.trim();

setReviewValue(
  "username",
  usernameValue
    ? `@${usernameValue}`
    : ""
);
Enter fullscreen mode Exit fullscreen mode

The password itself is not displayed.

Instead, the review screen shows masked dots:

const passwordDots =
  password.value
    ? "•".repeat(
        Math.min(
          password.value.length,
          12
        )
      )
    : "••••••••";
Enter fullscreen mode Exit fullscreen mode

Editing Information from the Review Step

The review cards include Edit buttons.

These buttons return the user to the relevant step:

editButtons.forEach((button) => {

  button.addEventListener(
    "click",
    (event) => {

      event.preventDefault();

      const stepNumber =
        Number(
          button.dataset.editStep
        );

      if (!stepNumber) return;

      showStep(stepNumber);

    }
  );

});
Enter fullscreen mode Exit fullscreen mode

This allows users to correct their information without restarting the form.


Show and Hide Password

The password visibility button switches the input between password and text:

passwordToggle.addEventListener(
  "click",
  (event) => {

    event.preventDefault();

    const hidden =
      password.type === "password";

    password.type =
      hidden
        ? "text"
        : "password";

    passwordToggle.setAttribute(
      "aria-label",
      hidden
        ? "Hide password"
        : "Show password"
    );

  }
);
Enter fullscreen mode Exit fullscreen mode

Updating the aria-label also keeps the control more accessible.


Terms Validation

Before the demo registration can be completed, the terms checkbox must be selected:

if (!terms.checked) {

  termsError.textContent =
    "Please accept the Terms of Service and Privacy Policy.";

  return;

}
Enter fullscreen mode Exit fullscreen mode

Once the checkbox is selected, the error is cleared automatically.


Success State

After the final validation succeeds, the registration wizard is hidden:

form.hidden = true;

if (progress) {
  progress.hidden = true;
}

if (header) {
  header.hidden = true;
}
Enter fullscreen mode Exit fullscreen mode

The success state is then displayed:

success.hidden = false;
Enter fullscreen mode Exit fullscreen mode

The user sees an Account Created! message and can start the demo again.


Resetting the Form

The Start Again button resets:

  • Form values
  • Validation states
  • Terms checkbox
  • Bio counter
  • Password strength
  • Password visibility
  • Progress indicator
  • Current step

The form then returns to Step 1.

This makes the component easy to test repeatedly.


Where Can You Use This Component?

A multi-step registration form can be adapted for many interfaces, including:

  • Account registration
  • SaaS onboarding
  • Membership websites
  • Developer communities
  • Portfolio platforms
  • Application forms
  • Profile setup
  • Multi-stage contact forms

The current CodeRipple component is a front-end demo.

For a real registration system, you would still need backend processing, secure password handling, server-side validation and database integration.


Live Demo and Complete Source Code

You can test the complete component and copy the full HTML, CSS and JavaScript on CodeRipple:

👉 Multi Step Registration Form - Full Tutorial

You can also experiment with the live component on CodePen:

👉 Multi Step Registration Form - CodePen Demo


Final Thoughts

Breaking a registration process into smaller steps can make a larger form easier to understand and navigate.

In this component:

  • HTML provides the three-step form structure
  • CSS creates the responsive dark interface
  • JavaScript handles navigation, validation, password feedback, review data and the success state

You can customize the colors, fields, validation rules and number of steps to fit your own project.

For more ready-to-use HTML, CSS and JavaScript components, visit CodeRipple.

Top comments (0)