NEW
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
Learn more
Skip to content
Docs

Fluid Tabs

The component is a sliding animation card

Loading...

Installation

CLI

pnpm dlx shadcn@latest add https://animata.design/r/tabs/fluid-tabs.json

Manual

Install dependencies

npm install motion lucide-react

Run the following command

It will create a new file fluid-tabs.tsx inside the components/animata/tabs directory.

mkdir -p components/animata/tabs && touch components/animata/tabs/fluid-tabs.tsx

Paste the code

Open the newly created file and paste the following code:

"use client";
 
import { Inbox, Landmark, PieChart } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { useEffect, useRef, useState } from "react";
 
const tabs = [
  {
    id: "accounts",
    label: "Accounts",
    icon: <Landmark size={18} />,
  },
  {
    id: "deposits",
    label: "Deposits",
    icon: <Inbox size={18} />,
  },
  {
    id: "funds",
    label: "Funds",
    icon: <PieChart size={18} />,
  },
];
 
export default function FluidTabs() {
  const [activeTab, setActiveTab] = useState("funds");
  const [touchedTab, setTouchedTab] = useState<string | null>(null);
  const [prevActiveTab, setPrevActiveTab] = useState("funds");
  const timeoutRef = useRef<NodeJS.Timeout | null>(null);
 
  useEffect(() => {
    return () => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
    };
  }, []);
 
  const handleTabClick = (tabId: string) => {
    setPrevActiveTab(activeTab);
    setActiveTab(tabId);
    setTouchedTab(tabId);
 
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }
    timeoutRef.current = setTimeout(() => {
      setTouchedTab(null);
    }, 300);
  };
 
  const getTabIndex = (tabId: string) => tabs.findIndex((tab) => tab.id === tabId);
 
  return (
    <div className="flex items-center justify-center py-4">
      <div className="relative flex w-full max-w-md space-x-2 overflow-hidden rounded-full bg-[#f5f1eb] p-1 shadow-lg">
        <AnimatePresence initial={false}>
          <motion.div
            key={activeTab}
            className="absolute inset-y-0 my-1 rounded-full bg-white"
            initial={{ x: `${getTabIndex(prevActiveTab) * 100}%` }}
            animate={{ x: `${getTabIndex(activeTab) * 100}%` }}
            transition={{ type: "spring", stiffness: 300, damping: 30 }}
            style={{ width: `${100 / tabs.length}%` }}
          />
        </AnimatePresence>
        {tabs.map((tab) => (
          <motion.button
            key={tab.id}
            className={`relative z-10 flex w-full items-center justify-center gap-1.5 px-5 py-3 text-sm font-bold transition-colors duration-300 ${
              activeTab === tab.id ? "font-bold text-black" : "text-gray-500"
            } ${touchedTab === tab.id ? "blur-sm" : ""}`}
            onClick={() => handleTabClick(tab.id)}
          >
            {tab.icon}
            {tab.label}
          </motion.button>
        ))}
      </div>
    </div>
  );
}

Credits

Built by Rudra Sankha Sinhamahapatra
Twitter Handle Rudra Sankha