Navbar

This is a navbar component built using nextjs

framer-motion

Installation

Install the required dependencies:

npm install framer-motion
lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}
components/ui/navbar.tsx
"use client";
import Link from "next/link";
import React, { useState, useRef } from "react";

const buttonItems = [
  { index: 1, name: "Button 1", link: "#" },
  { index: 2, name: "Button 2", link: "#" },
  { index: 3, name: "Button 3", link: "#" },
  { index: 4, name: "Button 4", link: "#" },
  { index: 5, name: "Button 5", link: "#" },
];

export default function Navbar() {
  const [active, setActive] = useState(0);
  const [hover, setHover] = useState(-1);
  const containerRef = useRef<HTMLDivElement | null>(null);
  const itemRefs = useRef<(HTMLAnchorElement | null)[]>([]);

  const getStyle = () => {
    if (hover < 0) {
      return { left: "4px", width: "98.5%" };
    }

    const item = itemRefs.current[hover];
    if (!item || !containerRef.current) return {};

    const itemRect = item.getBoundingClientRect();
    const parentRect = containerRef.current.getBoundingClientRect();

    return {
      left: `${itemRect.left - parentRect.left}px`,
      width: `${itemRect.width}px`,
    };
  };

  return (
    <nav className="w-full h-fit flex justify-center items-center">
      <div className="relative p-[3px] inset-[3px] rounded-full bg-gradient-to-b from-neutral-700 to-neutral-800">
        <div
          ref={containerRef}
          className="relative w-fit rounded-full bg-gradient-to-b from-neutral-800 to-neutral-900 h-14 flex items-center"
        >
          <div
            className="absolute top-[5px] h-[46px] bg-gradient-to-b from-white/10 to-white/5 rounded-full transition-all duration-300"
            style={getStyle()}
          />

          <div className="flex z-10 gap-1">
            {buttonItems.map((item) => (
              <Link
                href={item.link}
                key={item.index}
                ref={(el) => {
                  itemRefs.current[item.index] = el;
                }}
                onMouseEnter={() => setHover(item.index)}
                onMouseLeave={() => setHover(-1)}
                onClick={() => setActive(item.index)}
                className="relative cursor-pointer px-6 mx-1 py-3 z-10"
              >
                <p
                  className={`transition-colors duration-200 ${
                    active === item.index ? "text-black" : "text-white"
                  }`}
                >
                  {item.name}
                </p>
              </Link>
            ))}
          </div>
        </div>
      </div>
    </nav>
  );
}

Usecase

* Navbar with some animations