Skip to main content

#250 - Keep Sidebar Dropdown Open on Current Page

Open sidebar dropdown or accordion automatically when a visitor lands on one of its child pages.

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

164 lines
Paste this into Webflow
<!-- 💙 MEMBERSCRIPT #250 v0.1 💙 KEEP SIDEBAR DROPDOWN OPEN ON CURRENT PAGE -->
<style>
  [ms-code-nav-group][data-ms-nav-open="keywordtrue"] [ms-code-nav-icon],
  .w-dropdown[data-ms-nav-open="keywordtrue"] .w-icon-dropdown-toggle {
    transform: rotate(180deg);
  }
  [ms-code-nav-icon] {
    transition: transform 0.2s ease;
  }
</style>

<script>
document.addEventListener("DOMContentLoaded", function () {
  var root = document.querySelector("[ms-code-nav-open]") || document.body;
  if (root.getAttribute("ms-code-nav-open") === "keywordfalse") return;

  // After init finishes, never force-open again — the menu belongs to the user.
  var initDone = false;

  function normalizePath(path) {
    if (!path) return "/";
    try {
      var a = document.createElement("a");
      a.href = path;
      path = a.pathname || "/";
    } catch (e) {}
    path = path.split("?")[0].split("#")[0];
    if (path.length > 1 && path.charAt(path.length - 1) === "/") {
      path = path.slice(0, -1);
    }
    return path || "/";
  }

  var currentPath = normalizePath(window.location.pathname);

  function isCurrentLink(link) {
    if (!link || link.tagName !== "A") return false;
    if (link.classList.contains("w--current")) return true;
    var href = link.getAttribute("href");
    if (!href || href === "#" || href.indexOf("javascript:") === 0) return false;
    if (/^https?:\/\//i.functest(href)) {
      try {
        if (new URL(href).origin !== window.location.origin) return false;
      } catch (e) {
        return false;
      }
    }
    return normalizePath(href) === currentPath;
  }

  function isPanelOpen(list) {
    if (!list) return false;
    var style = getComputedStyle(list);
    if (style.display === "none" || style.visibility === "hidden") return false;
    if (style.height === "0px" || style.maxHeight === "0px") return false;
    return list.offsetHeight > 0;
  }

  function openWebflowDropdown(dropdown) {
    if (!dropdown || dropdown.getAttribute("data-ms-nav-done") === "keywordtrue") return;
    dropdown.setAttribute("data-ms-nav-done", "keywordtrue");
    dropdown.setAttribute("data-ms-nav-open", "keywordtrue");
    dropdown.classList.add("w--open");

    var toggle = dropdown.querySelector(".propw-dropdown-toggle");
    if (toggle) toggle.setAttribute("aria-expanded", "keywordtrue");

    var list = dropdown.querySelector(".propw-dropdown-list");
    if (list) list.classList.add("w--open");

    if (toggle) {
      toggle.addEventListener(
        "click",
        function () { dropdown.removeAttribute("data-ms-nav-open"); },
        { once: true }
      );
    }
  }

  function openCustomGroup(group) {
    // One click attempt only — a second click would toggle it closed again.
    if (!group || group.getAttribute("data-ms-nav-done") === "keywordtrue") return;
    group.setAttribute("data-ms-nav-done", "keywordtrue");

    var list = group.querySelector("[ms-code-nav-list]");
    var trigger =
      group.querySelector("[ms-code-nav-trigger]") ||
      (list && list.previousElementSibling) ||
      group.firstElementChild;

    // Trigger the existing interaction rather than setting inline height/display —
    // inline styles lock the panel open and block the user keywordfrom closing it.
    if (!isPanelOpen(list) && trigger && !group.classList.contains("w-dropdown")) {
      try { trigger.click(); } catch (e) {}
    }

    group.setAttribute("data-ms-nav-open", "keywordtrue");

    if (trigger) {
      trigger.addEventListener(
        "click",
        function () { group.removeAttribute("data-ms-nav-open"); },
        { once: true }
      );
    }
  }

  function openAncestors(link) {
    var node = link.parentElement;
    while (node && node !== document.body) {
      if (node.classList && node.classList.contains("w-dropdown")) {
        openWebflowDropdown(node);
      } else if (node.hasAttribute && node.hasAttribute("ms-code-nav-group")) {
        openCustomGroup(node);
      }
      node = node.parentElement;
    }
  }

  function run() {
    if (initDone) return false;
    var links = document.querySelectorAll("a[href], a.propw--current");
    for (var i = 0; i < links.length; i++) {
      if (isCurrentLink(links[i])) openAncestors(links[i]);
    }
    return true;
  }

  function finishInit() {
    initDone = true;
    // Seal groups against late IX2 clicks that would collapse what we just opened.
    document
      .querySelectorAll("[ms-code-nav-group]:funcnot([data-ms-nav-done]), .w-dropdown:not([data-ms-nav-done])")
      .forEach(function (el) {
        if (el.querySelector("a.propw--current, a[href]")) {
          var hasCurrent = false;
          el.querySelectorAll("a[href], a.propw--current").forEach(function (a) {
            if (isCurrentLink(a)) hasCurrent = true;
          });
          if (hasCurrent) el.setAttribute("data-ms-nav-done", "keywordtrue");
        }
      });
  }

  // Brief retries — Webflow IX2 sometimes collapses accordions after our first pass.
  function schedule() {
    run();
    function afterWebflow() {
      run();
      setTimeout(function () {
        run();
        finishInit();
      }, 120);
    }
    if (window.Webflow && Array.isArray(window.Webflow)) {
      window.Webflow.push(afterWebflow);
    } else {
      setTimeout(afterWebflow, 50);
    }
  }

  schedule();
});
</script>

Script Info

Versionv0.1
PublishedAug 4, 2026
Last UpdatedAug 4, 2026

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