Stepper

<!-- Stepper Form Content -->
<form id="stepper-form" role="form" action="" method="post">

    <!-- Stepper Mobile Step Summary -->
    <div class="stepper-mobile-summary">
        Stap <span class="current-step">1</span> van <span class="step-count">6</span>
    </div>

    <!-- Stepper Steps -->
    <ul class="stepper stepper-horizontal stepper-steps">

        <!-- First Step -->
        <li aria-label="Actieve stap" class="active" id="step-1">

            <!-- Step Button -->
            <a class="step-link" href="#step-content-1">
                <span class="circle">1</span>
                <span class="label">Eerste stap</span>
            </a>

            <!-- Step Content -->
            <div class="step-content" id="step-content-1">
                <div class="col-md-12">
                    <h3 class="font-weight-bold pl-0 my-4"><strong>Basis informatie</strong></h3>
                    <div class="form-group md-form">
                        <input id="email" type="email" required="required" class="form-control validate" placeholder="Email">
                        <label for="email" data-error="error">Email</label>
                    </div>
                    <div class="form-group md-form">
                        <input id="username" type="text" required="required" class="form-control validate" placeholder="Gebruikersnaam">
                        <label for="username" data-error="required">Gebruikersnaam</label>
                    </div>
                    <button class="btn btn-primary nextBtn float-right" type="button">Volgende</button>
                </div>
            </div>

        </li>

        <!-- Second Step -->
        <li class="disabled" id="step-2">

            <!-- Step Button -->
            <a class="step-link" href="#step-content-2">
                <span class="circle">2</span>
                <span class="label">Stap 2</span>
            </a>

            <!-- Step Content -->
            <div class="step-content" id="step-content-2">
                <div class="col-md-12">
                    <h3 class="font-weight-bold pl-0 my-4"><strong>Persoonlijke data</strong></h3>
                    <div class="form-group md-form">
                        <input id="firstName" type="text" required="required" class="form-control validate" placeholder="Voornaam">
                        <label for="firstName" data-error="required">Voornaam</label>
                    </div>
                    <div class="form-group md-form mt-3">
                        <input id="secondName" type="text" required="required" class="form-control validate" placeholder="Achternaam">
                        <label for="secondName" data-error="required">Achternaam</label>
                    </div>
                    <button class="btn btn-primary backBtn float-left" type="button">Vorige</button>
                    <button class="btn btn-primary nextBtn float-right" type="button">Volgende</button>
                </div>
            </div>

        </li>

        <!-- Third Step -->
        <li class="disabled" id="step-3">

            <!-- Step Button -->
            <a class="step-link" href="#step-content-3">
                <span class="circle">3</span>
                <span class="label">Stap 3</span>
            </a>

            <!-- Step Content -->
            <div class="step-content" id="step-content-3">
                <div class="col-md-12">
                    <h3 class="font-weight-bold pl-0 my-4"><strong>Stap 3</strong></h3>
                    <div class="form-group md-form">
                        <input id="step3" type="text" required="required" class="form-control validate" placeholder="Stap 3 invoer">
                        <label for="step3" data-error="required">Stap 3 invoer</label>
                    </div>
                    <button class="btn btn-primary backBtn float-left" type="button">Vorige</button>
                    <button class="btn btn-primary nextBtn float-right" type="button">Volgende</button>
                </div>
            </div>

        </li>

        <!-- Fourth Step -->
        <li class="disabled" id="step-4">

            <!-- Step Button -->
            <a class="step-link" href="#step-content-4">
                <span class="circle">4</span>
                <span class="label">Stap 4</span>
            </a>

            <!-- Step Content -->
            <div class="step-content" id="step-content-4">
                <div class="col-md-12">
                    <h3 class="font-weight-bold pl-0 my-4"><strong>Stap 4</strong></h3>
                    <div class="form-group md-form">
                        <input id="step4" type="text" required="required" class="form-control validate" placeholder="Stap 4 invoer">
                        <label for="step4" data-error="required">Stap 4 invoer</label>
                    </div>
                    <button class="btn btn-primary backBtn float-left" type="button">Vorige</button>
                    <button class="btn btn-primary nextBtn float-right" type="button">Volgende</button>
                </div>
            </div>

        </li>

        <!-- Fifth Step -->
        <li class="disabled" id="step-5">

            <!-- Step Button -->
            <a class="step-link" href="#step-content-5">
                <span class="circle">5</span>
                <span class="label">Stap 5</span>
            </a>

            <!-- Step Content -->
            <div class="step-content" id="step-content-5">
                <div class="col-md-12">
                    <h3 class="font-weight-bold pl-0 my-4"><strong>Stap 5</strong></h3>
                    <div class="form-group md-form">
                        <input id="step5" type="text" required="required" class="form-control validate" placeholder="Stap 5 invoer">
                        <label for="step5" data-error="required">Stap 5 invoer</label>
                    </div>
                    <button class="btn btn-primary backBtn float-left" type="button">Vorige</button>
                    <button class="btn btn-primary nextBtn float-right" type="button">Volgende</button>

                    <!-- A submit button could be used at any point and will get triggered on step progression -->
                    <!-- <button class="btn btn-primary nextBtn float-right" type="submit">Submit</button> -->
                </div>
            </div>

        </li>

        <!-- Sixth Step -->
        <li class="disabled" id="step-6">

            <!-- Step Button -->
            <a class="step-link" href="#step-content-6">
                <span class="circle">6</span>
                <span class="label">Laatste stap</span>
            </a>

            <!-- Step Content -->
            <div class="step-content" id="step-content-6">
                <div class="col-md-12">
                    <h3 class="font-weight-bold pl-0 my-4"><strong>Voltooid</strong></h3>
                    <h4 class="text-center font-weight-bold my-4">Registratie compleet!</h4>
                </div>
            </div>
        </li>

    </ul>

    <!-- Stepper Mobile Navigation -->
    <div class="stepper-mobile-navigation">
        <a class="back-link float-left" href="#" aria-label="Vorige">
            <span aria-hidden="true" class="mdi mdi-chevron-left"></span>
            <span>Vorige</span>
        </a>
        <a class="next-link float-right" href="#" aria-label="Volgende">
            <span>Volgende</span>
            <span aria-hidden="true" class="mdi mdi-chevron-right"></span>
        </a>
    </div>

</form>

Stepper

Based on the version from MDB:
https://mdbootstrap.comcomponents/bootstrap-steps-stepper/

And, Google’s Material design:
https://material.io/archive/guidelines/components/steppers.html#steppers-types-of-steps

Accessibility

Make sure that the step bar has the right aria-label attributes applied to them. For the current step we expect the tab to have aria-label="Actieve stap", and when the step is completed aria-label="Voltooide stap".

Using

The stepper is designed to work with up to six steps and should appear only once per page.

The stepper allows both forward and backward progression once each step is valid.

Back buttons should only be included on the steps that need it. For instance, not step 1.

The step labels and form input can be changed freely to suit your needs.

The stepper uses a horizontal view on large width screen, vertical view on medium width screens and mobile view on small width screens.

As shown (commented out) within step 5 of the example, a submit button could be used at any point and will get triggered on step progression:

<button class="btn btn-primary nextBtn float-right" type="submit">Submit</button>

The submit button will post the form inputs, and could appear on any step.

The below JavaScript is required to use the Stepper component and should be placed in the Additional component(s) script section as documented in How to use.

<script>
    function areInputsValid(currentInputs, targetId, validateAllRequiredFields) {
        var isValid = true;
        for (var i = 0; i < currentInputs.length; i++) {
            $(currentInputs[i]).addClass("valid");
            if (!currentInputs[i].validity.valid){
                isValid = false;

                // Only show a required input is invalid for the current input or when validating all required
                if (validateAllRequiredFields || currentInputs[i].id === targetId) {
                    $(currentInputs[i]).addClass("invalid");
                }
            }
        }
        return isValid;
    }

    function updateBackButtons(navigationStep, mobileBackBtn) {
        if (navigationStep.find('.backBtn').length > 0) { // Check if step has back button
            mobileBackBtn.show();
        } else {
            mobileBackBtn.hide();
        }
    }

    function isValidStep(stepNumber, validSteps) {
        return $.inArray(stepNumber, validSteps) > -1;
    }

    function setNavigationItemStatuses(navigationListItems, navigationStep, currentStepNumber, stepCount, validSteps) {
        navigationStep.prevAll().removeClass('disabled').addClass('completed').removeClass('active').attr('aria-label', 'Voltooide stap');
        navigationStep.removeClass('disabled').removeClass('completed').addClass('active').attr('aria-label', 'Actieve stap');
        var currentStepIsValid = isValidStep(currentStepNumber, validSteps);
        if (!currentStepIsValid) {
            navigationStep.nextAll().addClass('disabled').removeClass('active').removeClass('completed').removeAttr('aria-label');
            return;
        }

        navigationStep.nextAll().removeClass('active');
        var previousStepValid = true;
        for (var i = currentStepNumber + 1; i <= stepCount; i++) {
            if (isValidStep(i, validSteps) && previousStepValid) {
                navigationListItems.eq(i - 1).parent().removeClass('disabled').addClass('completed').attr('aria-label', 'Voltooide stap');
            } else if (previousStepValid) {
                navigationListItems.eq(i - 1).parent().removeClass('disabled').removeClass('completed').removeAttr('aria-label');
            } else {
                navigationListItems.eq(i - 1).parent().addClass('disabled').removeClass('completed').removeAttr('aria-label');
            }
            previousStepValid = isValidStep(i, validSteps) && previousStepValid;
        }
        navigationStep.next().removeClass('disabled');
    }

    function updateButtons(
        navigationListItems,
        navigationStep,
        allBackBtn,
        allNextBtn,
        mobileBackBtn,
        mobileNextBtn,
        currentStepNumber,
        stepCount,
        validSteps
    ) {
        setNavigationItemStatuses(navigationListItems, navigationStep, currentStepNumber, stepCount, validSteps);
        mobileNextBtn.show();
        if (navigationStep.find('.nextBtn').length > 0) { // Check if step has a next button
            updateBackButtons(navigationStep, mobileBackBtn);
        } else {
            mobileNextBtn.hide();
        }
    }

    function updateStepStatus(
        navigationListItems,
        navigationStep,
        allBackBtn,
        allNextBtn,
        mobileBackBtn,
        mobileNextBtn,
        currentStepNumber,
        stepCount,
        validSteps
    ) {
        updateButtons(
            navigationListItems,
            navigationStep,
            allBackBtn,
            allNextBtn,
            mobileBackBtn,
            mobileNextBtn,
            currentStepNumber,
            stepCount,
            validSteps
        );
        $('.current-step').text(currentStepNumber);
        $('.step-count').text(stepCount);
    }

    // Stepper Form
    $(document).ready(function () {
        var navigationListItems = $('.stepper-steps li a'),
            allContent = $('.step-content'),
            allBackBtn = $('.backBtn'),
            allNextBtn = $('.nextBtn'),
            mobileBackBtn = $('.back-link'),
            mobileNextBtn = $('.next-link'),
            activeStep = $('.stepper-steps li.active a'),
            stepperForm = $('#stepper-form'),
            stepperFormInputs = $('#stepper-form :input'),
            validSteps = [];

        allContent.hide();

        stepperFormInputs.on('change keyup paste', function(event) {
            var currentStepContent = $(this).closest(".step-content"),
                currentStep = $('.stepper-steps li a[href="#' + currentStepContent.attr("id") + '"]').parent(),
                currentStepNumber = parseInt(currentStepContent.attr("id").substring(13)),
                currentInputs = currentStepContent.find("input");

            currentStep.removeClass('disabled');
            var stepPositionInArray = $.inArray(currentStepNumber, validSteps);
            var stepIsValid = isValidStep(currentStepNumber, validSteps);
            if (areInputsValid(currentInputs, event.target.id, false)) {
                if (!stepIsValid) validSteps.push(currentStepNumber);
            } else {
                if (stepIsValid) validSteps.splice(stepPositionInArray, 1);
            }
            updateStepStatus(
                navigationListItems,
                currentStep,
                allBackBtn,
                allNextBtn,
                mobileBackBtn,
                mobileNextBtn,
                currentStepNumber,
                allContent.length,
                validSteps
            );
        });

        navigationListItems.click(function (event) {
            event.preventDefault();
            var $target = $($(this).attr('href')),
                $item = $(this);

            if (!$item.hasClass('disabled')) {
                if ($item.parent().prev().find('button[type="submit"]').length) {
                    stepperForm.submit();
                }
                allContent.hide();
                $target.show();
                $target.find('input:eq(0)').focus();
                updateStepStatus(
                    navigationListItems,
                    $item.parent(),
                    allBackBtn,
                    allNextBtn,
                    mobileBackBtn,
                    mobileNextBtn,
                    parseInt($item.attr('href').substring(14)),
                    allContent.length,
                    validSteps
                );
            }
        });

        mobileBackBtn.add(allBackBtn).click(function(){
            var currentStepContent = $('.stepper-steps li.active').children('.step-content'),
                previousStep = $('.stepper-steps li a[href="#' + currentStepContent.attr("id") + '"]').parent().prev().children("a");
            previousStep.trigger('click');
        });

        mobileNextBtn.add(allNextBtn).click(function(){
            var currentStepContent = $('.stepper-steps li.active').children('.step-content'),
                nextStep = $('.stepper-steps li a[href="#' + currentStepContent.attr("id") + '"]').parent().next().children("a"),
                currentInputs = currentStepContent.find("input");

            if (areInputsValid(currentInputs, null, true)) {
                nextStep.trigger('click');
            }
        });

        activeStep.trigger('click');

        $(window).keydown(function(event){
            if((event.keyCode == 13) && !$(event.target).is(
                '.back-link, .backBtn, .next-link, .nextBtn, .step-link'
            )) {
                event.preventDefault();
                return false;
            }
        });
    });
</script>

Notes

  • When renaming the id, href, aria-controls and/or aria-labelledby for your use-case, be sure to rename all occurrences
  • In the list with steps, each step has its own aria-label telling the status of the step
    • For completed steps, there is an aria-label="Voltooide stap"
    • For the current step, there is an aria-label="Actieve stap"