Ghost Dock

It is a stylish, interactive navigation bar built with Framer Motion and Tailwind CSS. It features a responsive, sliding background glow effect that tracks the active tab and supports custom icons.

Live Preview

Dependencies

npm installframer-motionlucide-react

Code

"use client";
import { motion, AnimatePresence } from "framer-motion";
import {
  Home,
  Bell,
  User,
  Hash,
  AlarmClock,
  BellOff,
  Menu,
  X,
} from "lucide-react";
import { ReactNode, useState } from "react";

type NavItem = {
  id: string;
  icon: ReactNode;
  label: string;
};
const NAV_ITEMS: NavItem[] = [
  { id: "home", icon: <Home size={20} />, label: "Home" },
  { id: "explore", icon: <Hash size={20} />, label: "Explore" },
  { id: "alerts", icon: <Bell size={20} />, label: "Alerts" },
  { id: "profile", icon: <User size={20} />, label: "Profile" },
  { id: "alarm", icon: <AlarmClock size={20} />, label: "Alarm" },
  { id: "silent", icon: <BellOff size={20} />, label: "Silent" },
];

export default function GhostDock({ items = NAV_ITEMS }) {
  const [active, setActive] = useState("home");
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className="relative">
      <AnimatePresence>
        {isOpen && (
          <motion.div
            initial={{ opacity: 0, y: 10, scale: 0.95 }}
            animate={{ opacity: 1, y: 0, scale: 1 }}
            exit={{ opacity: 0, y: 10, scale: 0.95 }}
            transition={{ type: "spring", bounce: 0.2, duration: 0.6 }}
            className="absolute bottom-full left-1/2 -translate-x-1/2 mb-3 flex flex-col items-center gap-2 p-2 shadow-[inset_0_2px_4px_rgba(0,0,0,0.3)] dark:shadow-[inset_0_2px_4px_rgba(255,255,255,0.3)] rounded-[2rem] bg-zinc-900/5 dark:bg-zinc-100/5 ring-2 ring-zinc-200 dark:ring-zinc-800 backdrop-blur-md md:hidden"
          >
            {items.map((item: NavItem, idx: number) => (
              <button
                key={idx}
                onClick={() => {
                  setActive(item.id);
                  setIsOpen(false);
                }}
                className="relative p-4 rounded-2xl text-zinc-500 hover:text-zinc-900 dark:hover:text-white transition-colors cursor-pointer"
              >
                {active === item.id && (
                  <motion.div
                    layoutId="dock-glow"
                    className="absolute inset-0 shadow-[inset_0_2px_4px_rgba(0,0,0,0.3)] dark:shadow-[inset_0_2px_4px_rgba(255,255,255,0.3)] bg-white dark:bg-zinc-800 rounded-3xl z-0"
                    transition={{ type: "spring", bounce: 0.2, duration: 0.6 }}
                  />
                )}
                <span className="relative z-10">{item.icon}</span>
              </button>
            ))}
          </motion.div>
        )}
      </AnimatePresence>

      <div className="flex items-center gap-2 p-2 shadow-[inset_0_2px_4px_rgba(0,0,0,0.3)] dark:shadow-[inset_0_2px_4px_rgba(255,255,255,0.3)] rounded-[2rem] bg-zinc-900/5 dark:bg-zinc-100/5 ring-2 ring-zinc-200 dark:ring-zinc-800 backdrop-blur-md">
        <div className="hidden md:flex items-center gap-2">
          {items.map((item: NavItem, idx: number) => (
            <button
              key={idx}
              onClick={() => setActive(item.id)}
              className="relative p-4 rounded-2xl text-zinc-500 hover:text-zinc-900 dark:hover:text-white transition-colors cursor-pointer"
            >
              {active === item.id && (
                <motion.div
                  layoutId="dock-glow"
                  className="absolute inset-0 shadow-[inset_0_2px_4px_rgba(0,0,0,0.3)] dark:shadow-[inset_0_2px_4px_rgba(255,255,255,0.3)] bg-white dark:bg-zinc-800 rounded-3xl z-0"
                  transition={{ type: "spring", bounce: 0.2, duration: 0.6 }}
                />
              )}
              <span className="relative z-10">{item.icon}</span>
            </button>
          ))}
        </div>

        <div className="md:hidden flex items-center">
          <button
            onClick={() => setIsOpen(!isOpen)}
            className="relative p-4 rounded-2xl text-zinc-500 hover:text-zinc-900 dark:hover:text-white transition-colors cursor-pointer"
          >
            <span className="relative z-10 flex items-center justify-center">
              {isOpen ? <X size={20} /> : <Menu size={20} />}
            </span>
          </button>
        </div>
      </div>
    </div>
  );
}

Props

PropTypeDefaultDescription
itemsNavItem[]NAV_ITEMSAn array of navigation items, each containing an ID, icon element, and label to populate the dock.

Features

Fluid Animations

Powered by Framer Motion's layoutId for smooth, spring-based transitions between active states.

Adaptive Styling

Features a polished, glassmorphic design with Tailwind CSS and backdrop blur for modern aesthetics.

Dark Mode Ready

Seamlessly adapts to both light and dark themes with dedicated shadow and background styles.

Interactive Navigation

Click an item to activate it, updating the layout while keeping navigation elements responsive.

Compact Footprint

Designed to be responsive and space-efficient, fitting easily into various screen sizes and UI layouts.