NEW
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
Learn more
Skip to content
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.

Loading...

Installation

CLI

pnpm dlx shadcn@latest add https://animata.design/r/container/animated-dock.json

Manual

Install dependencies

npm install 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 { Menu, X } from "lucide-react"; // Importing icons from lucide-react
import {
  AnimatePresence, // Enables animation presence detection
  type 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 "motion/react";
import Link from "next/link"; // Next.js Link component for navigation
import type React from "react"; // Importing React hooks
import { useRef, useState } from "react";
import { cn } from "@/lib/utils"; // Import utility for conditional class names
 
// 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