Design
Tabs
Magnetic Tab

Magnetic Tab

Implement a Magnetic tab component using Next.js, Tailwind CSS, and Framer Motion.

Preview

Installation

Install dependencies

pnpm add clsx tailwind-merge framer-motion

Add util file

utils/cn.ts
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]): string {
  return twMerge(clsx(inputs));
}

Copy the source code

components/ui/magneticTab.tsx
"use client";
 
import React, { useRef, useState } from "react";
 
interface MagneticTabProps {
  item: { id: number; text: string };
}
 
interface HoverPosition {
  x: number;
  y: number;
  opacity: number;
}
 
function MagneticTab({ item }: MagneticTabProps): React.JSX.Element {
  const buttonRef = useRef<HTMLButtonElement>(null);
 
  const [hoverPosition, setHoverPosition] = useState<HoverPosition>({
    x: 0,
    y: 0,
    opacity: 0,
  });
 
  const handleMouseMove = (e: React.MouseEvent<HTMLButtonElement>): void => {
    const { clientX, clientY, currentTarget } = e;
    const { left, top, width, height } = currentTarget.getBoundingClientRect();
    const x = (clientX - left - width / 2) * 0.15;
    const y = (clientY - top - height / 2) * 0.15;
 
    setHoverPosition({ x, y, opacity: 1 });
  };
 
  const onMouseOut = (): void => {
    setHoverPosition({ x: 0, y: 0, opacity: 0 });
  };
 
  return (
    <button
      type="button"
      ref={buttonRef}
      className="relative h-9"
      onMouseMove={handleMouseMove}
      onMouseLeave={onMouseOut}
    >
      <span className="relative z-10 px-4 py-2 text-sm text-zinc-600 transition-colors hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100">
        {item.text}
      </span>{" "}
      <div
        className="absolute bottom-0 left-0 h-full w-full rounded-[4px] bg-blue-300 transition-opacity dark:bg-blue-700/20"
        aria-hidden="true"
        style={{
          transform: `translate(${hoverPosition.x as unknown as string}px, ${hoverPosition.y as unknown as string}px)`,
          opacity: hoverPosition.opacity,
        }}
      />
    </button>
  );
}
 
export default MagneticTab;

Use the component

import MagneticTab from "@/components/ui/magneticTab";
 
const tabs = [
  { id: 1, text: "Home" },
  { id: 2, text: "Blog" },
  { id: 3, text: "Projects" },
];
 
export function MagneticTabDemo(): React.JSX.Element {
  return (
    <div className="flex flex-row ">
      {tabs.map((item) => (
        <MagneticTab key={item.id} item={item} />
      ))}
    </div>
  );
}

File Structure of the project

        • magneticTab.tsx
      • cn.ts