Docs

Animated Tabs

Tabs to switch content, click on a tab to check background animation.

Installation

Install dependencies

npm install framer-motion lucide-react

Update tailwind.config.js

Add the following to your tailwind.config.js file.

module.exports = {
  theme: {
    extend: {
    }
  }
}

Run the following command

It will create a new file tabs.tsx inside the components/mage-ui/hero directory.

mkdir -p components/mage-ui/hero && touch components/mage-ui/hero/tabs.tsx

Paste the code

Open the newly created file and paste the following code:

"use client";
 
import { useState } from "react";
import { motion } from "framer-motion";
import Image from "next/image";
import { cn } from "@/lib/utils";
 
type Tab = {
  title: string;
  value: string;
  content?: React.ReactNode;
};
 
export default function TabsPage() {
  const tabs: Tab[] = [
    {
      title: "Product",
      value: "product",
      content: <TabContent title="Product Tab" />,
    },
    {
      title: "Services",
      value: "services",
      content: <TabContent title="Services Tab" />,
    },
    {
      title: "Playground",
      value: "playground",
      content: <TabContent title="Playground Tab" />,
    },
    {
      title: "Content",
      value: "content",
      content: <TabContent title="Content Tab" />,
    },
    {
      title: "Random",
      value: "random",
      content: <TabContent title="Random Tab" />,
    },
  ];
 
  return (
    <div className="h-[20rem] md:h-[40rem] [perspective:1000px] relative flex flex-col max-w-5xl mx-auto w-full items-start justify-start my-40">
      <Tabs tabs={tabs} />
    </div>
  );
}
 
const Tabs = ({
  tabs: propTabs,
}: {
  tabs: Tab[];
}) => {
  const [active, setActive] = useState<Tab>(propTabs[0]);
  const [tabs, setTabs] = useState<Tab[]>(propTabs);
  const [hovering, setHovering] = useState(false);
 
  const moveSelectedTabToTop = (idx: number) => {
    const newTabs = [...propTabs];
    const selectedTab = newTabs.splice(idx, 1);
    newTabs.unshift(selectedTab[0]);
    setTabs(newTabs);
    setActive(newTabs[0]);
  };
 
  return (
    <>
      <div className="flex flex-row items-center justify-start relative overflow-auto sm:overflow-visible no-visible-scrollbar max-w-full w-full">
        {propTabs.map((tab, idx) => (
          <button
            key={tab.title}
            onClick={() => moveSelectedTabToTop(idx)}
            onMouseEnter={() => setHovering(true)}
            onMouseLeave={() => setHovering(false)}
            className="relative px-4 py-2 rounded-full"
            style={{ transformStyle: "preserve-3d" }}
          >
            {active.value === tab.value && (
              <motion.div
                layoutId="clickedbutton"
                transition={{ type: "spring", bounce: 0.3, duration: 0.6 }}
                className="absolute inset-0 bg-gray-200 dark:bg-zinc-800 rounded-full"
              />
            )}
            <span className="relative block text-black dark:text-white">
              {tab.title}
            </span>
          </button>
        ))}
      </div>
      <FadeInDiv tabs={tabs} active={active} hovering={hovering} />
    </>
  );
};
 
const FadeInDiv = ({
  tabs,
  active,
  hovering,
}: {
  tabs: Tab[];
  active: Tab;
  hovering?: boolean;
}) => {
  return (
    <div className="relative w-full h-full mt-32">
      {tabs.map((tab, idx) => (
        <motion.div
          key={tab.value}
          layoutId={tab.value}
          style={{
            scale: 1 - idx * 0.1,
            top: hovering ? idx * -50 : 0,
            zIndex: -idx,
            opacity: idx < 3 ? 1 - idx * 0.1 : 0,
          }}
          animate={{ y: tab.value === active.value ? [0, 40, 0] : 0 }}
          className="w-full h-full absolute top-0 left-0"
        >
          {tab.content}
        </motion.div>
      ))}
    </div>
  );
};
 
const TabContent = ({ title }: { title: string }) => {
  return (
    <div className="w-full overflow-hidden relative h-full rounded-2xl p-10 text-xl md:text-4xl font-bold text-white bg-gradient-to-br from-purple-700 to-violet-900">
      <p>{title}</p>
      <Image
        src="/linear.webp"
        alt="dummy image"
        width={1000}
        height={1000}
        className="object-cover object-left-top h-[60%] md:h-[90%] absolute -bottom-10 inset-x-0 w-[90%] rounded-xl mx-auto"
      />
    </div>
  );
};