Skip to content

Instantly share code, notes, and snippets.

@axelsegebrecht
Last active August 11, 2025 22:51
Show Gist options
  • Select an option

  • Save axelsegebrecht/369f9108a4c51107ff6fce40215db945 to your computer and use it in GitHub Desktop.

Select an option

Save axelsegebrecht/369f9108a4c51107ff6fce40215db945 to your computer and use it in GitHub Desktop.
JavaScript workaround for Hubspot multi-step form validation issue
<script type="text/javascript">
//
// JavaScript workaround to Hubspot multi-step form's issue
// Ref. https://community.hubspot.com/t5/HubSpot-Ideas/Multi-Step-Forms-Required-Fields
//
// Simply add this entire <script> block to the footer of any page that contains the
// Hubspot multi-step form.
// __ __ ___
// / / ___ / / _______ __ _____ ____ ___ ___ / (_)__ ___
// / _ \/ -_) / _ \/ __/ _ `/ |/ / -_) __/ / _ \/ _ \/ / / _ \/ -_)
// /_.__/\__/ /_.__/_/ \_,_/|___/\__/_/ \___/_//_/_/_/_//_/\__/
//
// Copyright © 2025 Axel Segebrecht (be braver Ltd)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//
// Wait for DOM to be ready
document.addEventListener('DOMContentLoaded', function() {
function validateHubspotForm(button) {
// Find the parent step container
var stepContainer = button.closest('.hsfc-Step');
if (!stepContainer) return;
// Get all required fields within this step only
var requiredFields = stepContainer.querySelectorAll('[aria-required="true"]');
// Check if all required fields in this step have values
var allFieldsFilled = Array.prototype.every.call(requiredFields, function(field) {
return field.value.trim() !== '';
});
// Update button state
button.disabled = !allFieldsFilled;
button.style.opacity = allFieldsFilled ? '1' : '0.5';
button.style.cursor = allFieldsFilled ? 'pointer' : 'not-allowed';
}
// Add event listeners using event delegation
document.addEventListener('input', function(event) {
if (event.target.getAttribute('aria-required') === 'true') {
// Find the next button in the same step
var stepContainer = event.target.closest('.hsfc-Step');
if (stepContainer) {
var nextButton = stepContainer.querySelector('[data-hsfc-id="Button"]');
if (nextButton) {
validateHubspotForm(nextButton);
}
}
}
});
document.addEventListener('change', function(event) {
if (event.target.getAttribute('aria-required') === 'true') {
// Find the next button in the same step
var stepContainer = event.target.closest('.hsfc-Step');
if (stepContainer) {
var nextButton = stepContainer.querySelector('[data-hsfc-id="Button"]');
if (nextButton) {
validateHubspotForm(nextButton);
}
}
}
});
// Initial validation for all steps
function initializeAllSteps() {
var allButtons = document.querySelectorAll('[data-hsfc-id="Button"]');
allButtons.forEach(function(button) {
validateHubspotForm(button);
});
}
initializeAllSteps();
// Run validation when new elements are added to the DOM (for dynamically loaded forms)
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes.length) {
initializeAllSteps();
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});
</script>
@tpkemme
Copy link

tpkemme commented Aug 11, 2025

This does not work for forms with dropdowns as they are not emitting change events.

I also suspect this solution is overcomplicating validation. Instead of trying to hook into input change, I never disabled the next button and hook validation into 'next' button click. If the fields aren't filled out, i just show a simple error message at the top. It's quick and dirty but at least it works.

    function validateHubspotForm(button) {
	  // Find the parent step container
	  var stepContainer = button.closest('.hsfc-Step');
	  if (!stepContainer) return;
	  
	  // Get all required fields within this step only
	  var requiredFields = stepContainer.querySelectorAll('[aria-required="true"]');
	  
	  // Check if all required fields in this step have values
	  var allFieldsFilled = Array.prototype.every.call(requiredFields, function(field) {
		return field.value.trim() !== '';
	  });

      return allFieldsFilled;
    }

    window.addEventListener('hs-form-event:on-ready', function() {
        document.querySelectorAll('.hsfc-Button').forEach(function(button) {
            var stepContent = button.closest('.hsfc-Step__Content');
            if (button.textContent.trim() === 'Next') {
                button.addEventListener('click', function(e) {
                    if (!validateHubspotForm(button)) {
                        e.preventDefault();
                        e.stopImmediatePropagation();
                        e.stopPropagation();
                        if (stepContent) {
                            if (!stepContent.querySelector('.hsfc-ErrorAlert-custom')) {
                                const errorDiv = document.createElement('div');
                                errorDiv.setAttribute('aria-live', 'polite');
                                errorDiv.className = 'hsfc-ErrorAlert-custom';
                                errorDiv.style.display = 'block';
                                errorDiv.style.color = 'red';
                                errorDiv.style.marginBottom = '16px';
                                errorDiv.style.fontSize = '14px';
                                errorDiv.textContent = 'Please complete all required fields.';
                                stepContent.insertBefore(errorDiv, stepContent.firstChild);
                            }
                        }
                        return false;
                    } else {
                        stepContent.querySelectorAll('.hsfc-ErrorAlert-custom').forEach(function(alert) {
                            alert.remove();
                        });
                    }
                });
            }
        });
    });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment