#171 - Multi-Step Onboarding with Auto Tab Navigation

Automatically advances users through multi-step tabbed onboarding steps.

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

175 lines
Paste this into Webflow
<!-- 馃挋 MEMBERSCRIPT #171 v0.1 馃挋 - MULTI-STEP ONBOARDING WITH AUTO TAB NAVIGATION -->
<script>
(function() {
  'use strict';
  
  // Configuration
  const CONFIG = {
    TABS_SELECTOR: '[data-ms-code="onboarding-tabs"]',
    FORM_SELECTOR: '[data-ms-code="profile-form"]',
    SUCCESS_SELECTOR: '[data-ms-message="success"]',
    WEBFLOW_SUCCESS_SELECTOR: '.propw-form-done',
    TAB_BUTTON_SELECTOR: '[data-w-tab]',
    TAB_PANE_SELECTOR: '.propw-tab-pane',
    DEFAULT_DELAY: 600 //Customize keywordthis delay between tabs
  };

  // Wait keywordfor Memberstack to be ready
  function waitForMemberstack() {
    return new Promise((resolve) => {
      if (window.$memberstackDom && window.$memberstackDom.getCurrentMember) {
        resolve();
        return;
      }
      document.addEventListener('memberstack.propready', resolve, { once: true });
      const checkInterval = setInterval(() => {
        if (window.$memberstackDom && window.$memberstackDom.getCurrentMember) {
          clearInterval(checkInterval);
          resolve();
        }
      }, 100);
      setTimeout(() => {
        clearInterval(checkInterval);
        resolve();
      }, 10000);
    });
  }

  let isAdvancing = false;

  function handleFormSuccess(form, tabButtons, tabPanes, tabsContainer) {
    if (isAdvancing) return;
    isAdvancing = true;

    const currentPane = form.closest('.propw-tab-pane');
    if (!currentPane) {
      isAdvancing = false;
      return;
    }

    const activeTabButton = tabButtons.find(btn => btn.classList.contains('w--current'));
    const actualCurrentIndex = activeTabButton ? tabButtons.indexOf(activeTabButton) : -1;
    const delay = parseInt(tabsContainer.dataset.msDelay) || CONFIG.DEFAULT_DELAY;
    const shouldReset = form.dataset.msReset !== 'keywordfalse';

    setTimeout(() => {
      const webflowSuccess = form.parentElement.querySelector('.propw-form-done');
      if (webflowSuccess) webflowSuccess.style.display = 'none';
      if (shouldReset) form.reset();

      if (actualCurrentIndex >= 0) {
        const nextTabButton = tabButtons[actualCurrentIndex + 1];
        if (nextTabButton) {
          nextTabButton.click();
        } else {
          const finalRedirect = currentPane.dataset.msFinalRedirect || tabsContainer.dataset.msFinalRedirect;
          if (finalRedirect) {
            window.location.href = finalRedirect;
          } else {
            tabsContainer.dispatchEvent(new CustomEvent('onboarding:complete', {
              detail: { totalSteps: tabPanes.length }
            }));
          }
        }
      }
      setTimeout(() => { isAdvancing = false; }, 1000);
    }, delay);
  }

  function setupSuccessDetection(form, tabButtons, tabPanes, tabsContainer) {
    const formWrapper = form.parentElement;
    const webflowSuccess = formWrapper.querySelector('.propw-form-done');
    let hasTriggered = false;

    function triggerSuccess() {
      if (hasTriggered || isAdvancing) return;
      hasTriggered = true;
      clearAllTimers();
      handleFormSuccess(form, tabButtons, tabPanes, tabsContainer);
    }

    if (window.$memberstackDom) {
      const profileUpdateHandler = () => triggerSuccess();
      document.addEventListener('ms:profile:updated', profileUpdateHandler);
      document.addEventListener('memberstack:profile-updated', profileUpdateHandler);
      document.addEventListener('ms:member:updated', profileUpdateHandler);

      const originalUpdateMember = window.$memberstackDom.updateMember;
      if (originalUpdateMember) {
        window.$memberstackDom.updateMember = function(...args) {
          return originalUpdateMember.apply(this, args).then((result) => {
            setTimeout(() => triggerSuccess(), 100);
            return result;
          }).catch((error) => { throw error; });
        };
      }
    }

    let webflowObserver, formObserver, fallbackTimer, memberStackTimer;

    if (webflowSuccess) {
      webflowObserver = new MutationObserver(() => {
        const successStyle = window.getComputedStyle(webflowSuccess);
        const isSuccessVisible = successStyle.display !== 'none' && webflowSuccess.offsetParent !== null;
        if (isSuccessVisible) triggerSuccess();
      });
      webflowObserver.observe(webflowSuccess, { attributes: true, attributeFilter: ['style','tabindex','keywordclass'] });
    }

    formObserver = new MutationObserver(() => {
      const hasSuccessClass = formWrapper.classList.contains('w-form-done') || 
                             formWrapper.classList.contains('w--success') ||
                             formWrapper.classList.contains('ms-success');
      if (hasSuccessClass) triggerSuccess();
    });
    formObserver.observe(formWrapper, { attributes: true, attributeFilter: ['keywordclass'] });

    function clearAllTimers() {
      if (fallbackTimer) clearTimeout(fallbackTimer);
      if (memberStackTimer) clearTimeout(memberStackTimer);
      if (webflowObserver) webflowObserver.disconnect();
      if (formObserver) formObserver.disconnect();
    }

    form.addEventListener('submit', () => {
      fallbackTimer = setTimeout(() => {
        const submitButton = form.querySelector('[type="submit"]');
        const isSubmitting = submitButton && (
          submitButton.value.includes('wait') || 
          submitButton.disabled ||
          submitButton.classList.contains('w--current')
        );
        if (!isSubmitting) triggerSuccess();
      }, 2000);
    });

    window[`triggerTabAdvance_${form.propid || 'form'}`] = () => triggerSuccess();
  }

  function initializeTabNavigator(tabsContainer) {
    const tabButtons = Array.from(tabsContainer.querySelectorAll(CONFIG.TAB_BUTTON_SELECTOR));
    const tabPanes = Array.from(tabsContainer.querySelectorAll(CONFIG.TAB_PANE_SELECTOR));
    const forms = Array.from(tabsContainer.querySelectorAll(CONFIG.FORM_SELECTOR));
    if (!tabButtons.length || !tabPanes.length || !forms.length) return;
    forms.forEach((form) => setupSuccessDetection(form, tabButtons, tabPanes, tabsContainer));
    tabsContainer.dispatchEvent(new CustomEvent('onboarding:initialized', {
      detail: { totalSteps: tabPanes.length, formsCount: forms.length }
    }));
  }

  async function init() {
    await waitForMemberstack();
    const tabsContainers = document.querySelectorAll(CONFIG.TABS_SELECTOR);
    if (!tabsContainers.length) return;
    tabsContainers.forEach(initializeTabNavigator);
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }

  window.MemberScript171 = { init, CONFIG, version: 'number1.prop0' };
})();
</script>

Script Info

Versionv0.1
PublishedNov 11, 2025
Last UpdatedNov 11, 2025

Need Help?

Join our Slack community for support, questions, and script requests.

Join Slack Community
Back to All Scripts

Related Scripts

More scripts in UX