Modal Simple

Modal
GSAP
JS
CMS
Last Updated: Dec 2, 2025
  • Modal children that close the modal like the close button or backdrop get an attribute of data-modal-1-close
  • The modal dialog has an attribute of data-modal-1-id="contact-modal".
  • The open button has an attribute of data-modal-1-trigger="contact-modal".
  • We can have multiple modals on the same page by changing the data-modal-id for each modal like shown in the example
  • We can use modals with the CMS by connecting the data-modal-1-id to the cms item's slug like shown in the example
  • To preview the modal, apply data-preview="true" to the dialog. Do not set the dialog manually to display block in the style panel since that overrides the dialog's native behavior
  • We can run other code when the modal opens with this script.
window.addEventListener("modal-1-open", (e) => {
  console.log(e.detail);
});
  • We can run more code when the modal closes with this script.
window.addEventListener("modal-1-close", (e) => {
  console.log(e.detail);
});
  • We can send users to the page with the modal pre-opened by including this behind the page url: ?modal-1-id=contact-modal
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
	document.querySelectorAll(".modal-1_dialog").forEach(function (component) {
		if (component.hasAttribute("data-modal-1")) return;
		component.setAttribute("data-modal-1", "");
		let lastFocusedElement;

		if (typeof gsap !== "undefined") {
			gsap.context(() => {
				component.tl = gsap.timeline({ paused: true, onReverseComplete: resetModal });
				component.tl.from(".modal-1_backdrop", { opacity: 0, duration: 0.2 });
				component.tl.from(".modal-1_content", { opacity: 0, y: "6rem", duration: 0.3 }, "<");
			}, component);
		}

		function resetModal() {
			typeof lenis !== "undefined" && lenis.start ? lenis.start() : (document.body.style.overflow = "");
			component.close();
			if (lastFocusedElement) lastFocusedElement.focus();
			window.dispatchEvent(new CustomEvent("modal-1-close", { detail: { component } }));
		}
		function openModal() {
			typeof lenis !== "undefined" && lenis.stop ? lenis.stop() : (document.body.style.overflow = "hidden");
			lastFocusedElement = document.activeElement;
			component.showModal();
			if (typeof gsap !== "undefined") component.tl.play();
			component.querySelectorAll(".modal-1_scroll").forEach((el) => (el.scrollTop = 0));
			window.dispatchEvent(new CustomEvent("modal-1-open", { detail: { component } }));
		}
		function closeModal() {
			typeof gsap !== "undefined" ? component.tl.reverse() : resetModal();
		}

		if (new URLSearchParams(location.search).get("modal-1-id") === component.getAttribute("data-modal-1-id")) openModal(), history.replaceState({}, "", ((u) => (u.searchParams.delete("modal-1-id"), u))(new URL(location.href)));
		component.addEventListener("cancel", (e) => (e.preventDefault(), closeModal()));
		component.addEventListener("click", (e) => e.target.closest("[data-modal-1-close]") && closeModal());
		document.addEventListener("click", (e) => e.target.closest("[data-modal-1-trigger='" + component.getAttribute("data-modal-1-id") + "']") && openModal());
	});
});
</script>