Tabs Simple

Tabs
GSAP
JS
CMS
Last Updated: Nov 8, 2025
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
	document.querySelectorAll(".tab-1_component").forEach((component, componentIndex) => {
		if (component.hasAttribute("data-tab-1")) return;
		component.setAttribute("data-tab-1", "");
    
		const allowLoop = true,
			buttonList = component.querySelector(".tab-1_button_list"),
			buttons = component.querySelectorAll(".tab-1_button_item"),
			panels = component.querySelectorAll(".tab-1_content_item"),
			prevButton = component.querySelector(".tab-1_control_button.is-previous"),
			nextButton = component.querySelector(".tab-1_control_button.is-next");

		if (!buttonList || !buttons.length || !panels.length) return;

		component.querySelector(".tab-1_content_list")?.removeAttribute("role");
		buttonList.setAttribute("role", "tablist");
		buttons.forEach((el) => el.setAttribute("role", "tab"));
		panels.forEach((el) => (el.setAttribute("role", "tabpanel"), el.style.display = "none"));

		let activeIndex = -1;
		function makeActive(index, animate = true) {
			if (activeIndex === index) return;
			buttons.forEach((btn, i) => {
				btn.classList.toggle("is-active", i === index);
				btn.setAttribute("aria-selected", i === index);
				btn.setAttribute("tabindex", i === index ? 0 : -1);
			});
			panels.forEach((el, i) => el.classList.toggle("is-active", i === index));
			buttonList.scrollTo({ left: buttons[index].offsetLeft, behavior: "smooth" });
			
			const prevPanel = panels[activeIndex];
			const nextPanel = panels[index];
			let tl = gsap.timeline({ defaults: { duration: 0.3 } });
			if (prevPanel) tl.to(prevPanel, { opacity: 0 });
			if (prevPanel) tl.set(prevPanel, { display: "none" });
			tl.set(nextPanel, { display: "block" });
			tl.fromTo(nextPanel, { opacity: 0 }, { opacity: 1 });
			
			if (!animate) tl.progress(1).kill();
			activeIndex = index;
			
			if (typeof ScrollTrigger !== "undefined") ScrollTrigger.refresh();
			if (nextButton) nextButton.disabled = index === buttons.length - 1 && !allowLoop;
			if (prevButton) prevButton.disabled = index === 0 && !allowLoop;
			if (animate) buttons[index].focus();
		};
		makeActive(0, false);

		const updateIndex = (delta) => makeActive((activeIndex + delta + buttons.length) % buttons.length);
		nextButton?.addEventListener("click", () => updateIndex(1));
		prevButton?.addEventListener("click", () => updateIndex(-1));

		buttons.forEach((btn, index) => {
			btn.id = `tab-1-button-${componentIndex}-${index}`;
			btn.setAttribute("aria-controls", `tab-1-panel-${componentIndex}-${index}`);
			panels[index].id = `tab-1-panel-${componentIndex}-${index}`;
			panels[index].setAttribute("aria-labelledby", btn.id);
			btn.addEventListener("click", () => makeActive(index));
			btn.addEventListener("keydown", (e) => {
				if (["ArrowRight", "ArrowDown"].includes(e.key)) updateIndex(1);
				else if (["ArrowLeft", "ArrowUp"].includes(e.key)) updateIndex(-1);
			});
		});
	});
});
</script>