Docs

Animated Dock

A sleek dock-style navigation bar, inspired by macOS, that combines glassmorphic design with functionality. With smooth animations and responsive icons, it enhances navigation for a modern web application.

Installation

Install dependencies

npm install framer-motion lucide-react

Run the following command

It will create a new file animated-dock.tsx inside the components/animata/container directory.

mkdir -p components/animata/container && touch components/animata/container/animated-dock.tsx

Paste the code

Open the newly created file and paste the following code:

import { cn } from "@/lib/utils"; // Import utility for conditional class names
import {
  AnimatePresence, // Enables animation presence detection
  MotionValue, // Type for motion values
  motion, // Main component for animations
  useMotionValue, // Hook to create a motion value
  useSpring, // Hook to create smooth spring animations
  useTransform, // Hook to transform motion values
} from "framer-motion";
import Link from "next/link"; // Next.js Link component for navigation
import React, { useRef, useState } from "react"; // Importing React hooks
import { Menu, X } from "lucide-react"; // Importing icons from lucide-react
 
// Interface for props accepted by the AnimatedDock component
interface AnimatedDockProps {
  items: { title: string; icon: React.ReactNode; href: string }[]; // Array of menu items
  largeClassName?: string; // Optional class name for large dock
  smallClassName?: string; // Optional class name for small dock
}
 
// Main AnimatedDock component that renders both LargeDock and SmallDock
export default function AnimatedDock({ items, largeClassName, smallClassName }: AnimatedDockProps) {
  return (
    <>
      {/* Render LargeDock for larger screens */}
      <LargeDock items={items} className={largeClassName} />
      {/* Render SmallDock for smaller screens */}
      <SmallDock items={items} className={smallClassName} />
    </>
  );
}
 
// Component for the large dock, visible on larger screens
const LargeDock = ({
  items,
  className,
}: {
  items: { title: string; icon: React.ReactNode; href: string }[]; // Items to display
  className?: string; // Optional class name
}) => {
  const mouseXPosition = useMotionValue(Infinity); // Create a motion value for mouse X position
  return (
    <motion.div
      onMouseMove={(e) => mouseXPosition.set(e.pageX)} // Update mouse X position on mouse move
      onMouseLeave={() => mouseXPosition.set(Infinity)} // Reset on mouse leave
      className={cn(
        "mx-auto hidden h-16 items-end gap-4 rounded-2xl bg-white/10 px-4 pb-3 dark:bg-black/10 md:flex", // Large dock styles
        className,
        "border border-gray-200/30 backdrop-blur-sm dark:border-gray-800/30",
      )}
    >
      {/* Render each dock icon */}
      {items.map((item) => (
        <DockIcon mouseX={mouseXPosition} key={item.title} {...item} />
      ))}
    </motion.div>
  );
};
 
// Component for individual icons in the dock
function DockIcon({
  mouseX,
  title,
  icon,
  href,
}: {
  mouseX: MotionValue; // Motion value for mouse position
  title: string; // Title of the icon
  icon: React.ReactNode; // Icon component
  href: string; // Link destination
}) {
  const ref = useRef<HTMLDivElement>(null); // Ref for measuring distance from mouse
 
  // Calculate the distance from the mouse to the icon
  const distanceFromMouse = useTransform(mouseX, (val) => {
    const bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 }; // Get icon bounds
    return val - bounds.x - bounds.width / 2; // Calculate distance from center
  });
 
  // Transform properties for width and height based on mouse distance
  const widthTransform = useTransform(distanceFromMouse, [-150, 0, 150], [40, 80, 40]);
  const heightTransform = useTransform(distanceFromMouse, [-150, 0, 150], [40, 80, 40]);
 
  // Transform properties for icon size based on mouse distance
  const iconWidthTransform = useTransform(distanceFromMouse, [-150, 0, 150], [20, 40, 20]);
  const iconHeightTransform = useTransform(distanceFromMouse, [-150, 0, 150], [20, 40, 20]);
 
  // Spring animations for smooth transitions
  const width = useSpring(widthTransform, { mass: 0.1, stiffness: 150, damping: 12 });
  const height = useSpring(heightTransform, { mass: 0.1, stiffness: 150, damping: 12 });
  const iconWidth = useSpring(iconWidthTransform, { mass: 0.1, stiffness: 150, damping: 12 });
  const iconHeight = useSpring(iconHeightTransform, { mass: 0.1, stiffness: 150, damping: 12 });
 
  const [isHovered, setIsHovered] = useState(false); // State for hover effect
 
  return (
    <Link href={href}>
      <motion.div
        ref={ref} // Reference for measuring
        style={{ width, height }} // Set dynamic width and height
        onMouseEnter={() => setIsHovered(true)} // Handle mouse enter
        onMouseLeave={() => setIsHovered(false)} // Handle mouse leave
        className="relative flex aspect-square items-center justify-center rounded-full bg-white/20 text-black shadow-lg backdrop-blur-md dark:bg-black/20 dark:text-white"
      >
        <AnimatePresence>
          {/* Tooltip that appears on hover */}
          {isHovered && (
            <motion.div
              initial={{ opacity: 0, y: 10, x: "-50%" }} // Initial animation state
              animate={{ opacity: 1, y: 0, x: "-50%" }} // Animation to visible state
              exit={{ opacity: 0, y: 2, x: "-50%" }} // Animation to exit state
              className="absolute -top-8 left-1/2 w-fit -translate-x-1/2 whitespace-pre rounded-md border border-gray-200 bg-white/80 px-2 py-0.5 text-xs text-neutral-700 dark:border-neutral-900 dark:bg-neutral-800 dark:text-white"
            >
              {title} {/* Tooltip text */}
            </motion.div>
          )}
        </AnimatePresence>
        <motion.div
          style={{ width: iconWidth, height: iconHeight }} // Set dynamic icon size
          className="flex items-center justify-center"
        >
          {icon} {/* Render the icon */}
        </motion.div>
      </motion.div>
    </Link>
  );
}
 
// Component for the small dock, visible on smaller screens
const SmallDock = ({
  items,
  className,
}: {
  items: { title: string; icon: React.ReactNode; href: string }[]; // Items to display
  className?: string; // Optional class name
}) => {
  const [isOpen, setIsOpen] = useState(false); // State to manage open/close of the small dock
 
  return (
    <div className={cn("relative block md:hidden", className)}>
      <AnimatePresence>
        {/* Render menu items when open */}
        {isOpen && (
          <motion.div
            layoutId="nav"
            className="absolute inset-x-0 bottom-full mb-2 flex flex-col gap-2"
          >
            {items.map((item, index) => (
              <motion.div
                key={item.title}
                initial={{ opacity: 0, y: 10 }} // Initial animation state
                animate={{ opacity: 1, y: 0 }} // Animation to visible state
                exit={{
                  opacity: 0,
                  y: 10,
                  transition: { delay: index * 0.05 }, // Delay based on index
                }}
                transition={{ delay: (items.length - 1 - index) * 0.05 }} // Delay for exit animations
              >
                <Link
                  href={item.href}
                  key={item.title}
                  className="flex h-10 w-10 items-center justify-center rounded-full bg-white/20 text-black shadow-md backdrop-blur-md dark:bg-black/20 dark:text-white"
                >
                  <div className="h-4 w-4">{item.icon}</div> {/* Render the icon */}
                </Link>
              </motion.div>
            ))}
          </motion.div>
        )}
      </AnimatePresence>
      {/* Button to toggle the small dock open/close */}
      <button
        onClick={() => setIsOpen(!isOpen)} // Toggle isOpen state on click
        className="flex h-10 w-10 items-center justify-center rounded-full bg-white/20 text-black shadow-md backdrop-blur-md dark:bg-black/20 dark:text-white"
      >
        {/* Render the appropriate icon based on open/close state */}
        {isOpen ? (
          <X className="h-5 w-5" /> // Show close icon when open
        ) : (
          <Menu className="h-5 w-5" /> // Show menu icon when closed
        )}
      </button>
    </div>
  );
};

Credits

Built by Eshan Singh with the help of Framer Motion. Inspired by Build UI