HomeComponentBottom Border Tab

Bottom Border Tab

Published Nov 20, 2024
By Vishal Karangale

Web development

Base Component

Copy and pase the following base component in your project
Make sure to install all the required dependencies

basic.tsx
"use client";
 
import { AnimatePresence, motion } from "framer-motion";
import type React from "react";
import { useLayoutEffect, useRef, useState } from "react";
 
interface Tab {
  id: string;
  title: string;
  content: React.ReactNode;
}
 
interface BottomBorderTabProps {
  tabs: Tab[];
  borderColor?: string;
  initialTabId?: string;
  activeTabClass?: string;
  inactiveTabClass?: string;
}
 
const BottomBorderTab: React.FC<BottomBorderTabProps> = ({
  tabs,
  borderColor = "bg-blue-500",
  initialTabId,
  activeTabClass = "text-white",
  inactiveTabClass = "text-primary",
}) => {
  const defaultTabId = initialTabId || tabs[0].id;
  const [activeTab, setActiveTab] = useState(defaultTabId);
  const [activeTabIndex, setActiveTabIndex] = useState<number>(
    tabs.findIndex((tab) => tab.id === defaultTabId),
  );
  const [sliderWidth, setSliderWidth] = useState<number>(0);
  const [sliderOffset, setSliderOffset] = useState<number>(0);
  const tabRefs = useRef<(HTMLButtonElement | null)[]>([]);
 
  const setTabRef = (index: number) => (el: HTMLButtonElement | null) => {
    tabRefs.current[index] = el;
  };
 
  useLayoutEffect(() => {
    const activeTabElement = tabRefs.current[activeTabIndex];
    if (activeTabElement) {
      setSliderWidth(activeTabElement.offsetWidth);
      setSliderOffset(activeTabElement.offsetLeft);
    }
  }, [activeTabIndex]);
 
  return (
    <div className="my-4 w-full overflow-hidden px-4 md:px-6 xl:px-20">
      <div className="relative w-full">
        <div className="flex w-full rounded-t-xl">
          {tabs.map((tab, index) => (
            <button
              key={tab.id}
              type="button"
              ref={setTabRef(index)}
              className={`flex-1 px-4 py-2 font-medium text-sm md:text-xl ${
                activeTab === tab.id ? activeTabClass : inactiveTabClass
              }`}
              onClick={() => {
                setActiveTab(tab.id);
                setActiveTabIndex(index);
              }}
            >
              {tab.title}
            </button>
          ))}
        </div>
        <motion.div
          className={`absolute bottom-0 h-0.5 ${borderColor}`}
          initial={false}
          animate={{
            width: sliderWidth,
            x: sliderOffset,
          }}
          transition={{ type: "spring", stiffness: 300, damping: 30 }}
        />
      </div>
      <AnimatePresence mode="wait">
        <motion.div
          key={activeTab}
          initial={{ opacity: 0, y: 20 }}
          animate={{ opacity: 1, y: 0 }}
          exit={{ opacity: 0, y: -20 }}
          transition={{ duration: 0.3 }}
          className="my-4 flex w-full flex-col gap-10 p-4"
        >
          {tabs.find((tab) => tab.id === activeTab)?.content}
        </motion.div>
      </AnimatePresence>
    </div>
  );
};
 
export default BottomBorderTab;

Component Props


Tab Item Props

PropTypeDefault
id
string
-
title
string
-
content
React.ReactNode
-

Bottom Border Tab Props

PropTypeDefault
tabs
Tab[]
-
initialTabId
string
-
borderColor
string
bg-blue-500
activeTabClass
string
-
inactiveTabClass
string
text-primary
Built with Next.js