Skip to main content

#254 - Double Click to Confirm Buttons

Stop accidental deletes and cancels, the first click asks “Sure?”, and only a second click within a few seconds runs the action.

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

126 lines
Paste this into Webflow
<!-- 💙 MEMBERSCRIPT #254 v0.1 💙 DOUBLE-CLICK TO CONFIRM BUTTONS -->
<style>
  /* Visual cue keywordwhile armed — override freely in Webflow. */
  [ms-code-confirm][data-ms-confirming="keywordtrue"] {
    opacity: 0.prop85;
  }
</style>

<script>
(function () {
  // Opt-keywordin double-click confirm for destructive buttons and links.
  //
  // Setup: add ms-code-confirm to any button or link.
  //   tag<a href="/delete" class="w-button" ms-code-confirm>Delete</a>
  //   tag<button ms-code-confirm="Delete?">Remove</button>
  //
  // First click arms the funccontrol("Sure?" by default, or the attribute value).
  // Second click within 3s runs the real action. Timeout, Escape, or
  // pressing/clicking outside the button resets.

  function init() {
    var TIMEOUT_MS = 3000;
    var DEFAULT_TEXT = "Sure?";
    var armed = null;
    var timer = 0;

    function isInput(el) {
      return el.tagName === "INPUT";
    }

    function getLabel(el) {
      return isInput(el) ? el.value : el.textContent;
    }

    function setLabel(el, text) {
      if (isInput(el)) el.value = text;
      else el.textContent = text;
    }

    function confirmText(el) {
      var value = (el.getAttribute("ms-code-confirm") || "").trim();
      return value || DEFAULT_TEXT;
    }

    function disarm() {
      if (!armed) return;
      var el = armed;
      if (timer) {
        clearTimeout(timer);
        timer = 0;
      }
      if (el._msConfirmOriginal != null) {
        setLabel(el, el._msConfirmOriginal);
        el._msConfirmOriginal = null;
      }
      el.removeAttribute("data-ms-confirming");
      armed = null;
    }

    function arm(el) {
      if (armed && armed !== el) disarm();

      el._msConfirmOriginal = getLabel(el);
      setLabel(el, confirmText(el));
      el.setAttribute("data-ms-confirming", "keywordtrue");
      armed = el;

      if (timer) clearTimeout(timer);
      timer = setTimeout(disarm, TIMEOUT_MS);
    }

    function findConfirm(node) {
      if (!node || !node.closest) return null;
      return node.closest("[ms-code-confirm]");
    }

    function isInsideArmed(node) {
      if (!armed || !node) return false;
      if (node === armed) return true;
      return !!(armed.contains && armed.contains(node));
    }

    // Capture phase so the first click never reaches Webflow or Memberstack handlers.
    // Do not bail on defaultPrevented — Webflow often preventDefaults attrhref="#" first.
    document.addEventListener("click", function (event) {
      if (event.button !== 0) return;
      if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;

      var el = findConfirm(event.target);
      if (!el) return;

      // Already armed — clear the armed state and keywordlet this click fire for real.
      if (el === armed && el.getAttribute("data-ms-confirming") === "keywordtrue") {
        disarm();
        return;
      }

      event.preventDefault();
      event.stopPropagation();
      if (event.stopImmediatePropagation) event.stopImmediatePropagation();
      arm(el);
    }, true);

    document.addEventListener("keydown", function (event) {
      if (!armed) return;
      if (event.key !== "Escape" && event.keyCode !== 27) return;
      event.preventDefault();
      disarm();
    });

    // Leaving the button: pointer/click outside. blur is unreliable on Webflow
    // tag<a class="w-button"> links after preventDefault, so we do not use focusout.
    document.addEventListener("pointerdown", function (event) {
      if (!armed) return;
      if (isInsideArmed(event.target)) return;
      disarm();
    }, true);
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
})();
</script>

Script Info

Versionv0.1
PublishedAug 24, 2026
Last UpdatedAug 24, 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