Components

Dock

A macOS-style dock component with magnification effects and smooth animations.

Last updated on

Edit on GitHub

Basic Usage

import { Calculator, Calendar, Compass, Mail, Music } from "lucide-react";
import {
  Dock,
  DockIcon,
  DockItem,
  DockLabel,
} from "@/components/ui/dock";
 
export function DockDemo() {
  return (
    <div className="flex h-32 items-end justify-center">
      <Dock>
        <DockItem>
          <DockIcon>
            <Compass />
          </DockIcon>
          <DockLabel>Safari</DockLabel>
        </DockItem>
 
        <DockItem>
          <DockIcon>
            <Mail />
          </DockIcon>
          <DockLabel>Mail</DockLabel>
        </DockItem>
 
        <DockItem>
          <DockIcon>
            <Calendar />
          </DockIcon>
          <DockLabel>Calendar</DockLabel>
        </DockItem>
 
        <DockItem>
          <DockIcon>
            <Music />
          </DockIcon>
          <DockLabel>Music</DockLabel>
        </DockItem>
 
        <DockItem>
          <DockIcon>
            <Calculator />
          </DockIcon>
          <DockLabel>Calculator</DockLabel>
        </DockItem>
      </Dock>
    </div>
  );
}

macOS Style

import {
  Calculator,
  Calendar,
  Camera,
  ChromeIcon,
  Clock,
  Image,
  Mail,
  MessageSquare,
  Music,
  Notebook,
  Settings,
  Store,
  Terminal,
  Trash2,
} from "lucide-react";
import {
  Dock,
  DockIcon,
  DockItem,
  DockLabel,
  DockSeparator,
} from "@/components/ui/dock";
 
export function DockMacosDemo() {
  return (
    <div className="flex h-32 items-end justify-center">
      <Dock magnification={80} distance={150}>
        <DockItem>
          <DockIcon>
            <ChromeIcon />
          </DockIcon>
          <DockLabel>Chrome</DockLabel>
        </DockItem>
        <DockItem>
          <DockIcon>
            <Calendar />
          </DockIcon>
          <DockLabel>Calendar</DockLabel>
        </DockItem>
 
        <DockItem>
          <DockIcon>
            <Mail />
          </DockIcon>
          <DockLabel>Mail</DockLabel>
        </DockItem>
 
        <DockItem>
          <DockIcon>
            <MessageSquare />
          </DockIcon>
          <DockLabel>Messages</DockLabel>
        </DockItem>
 
        <DockItem>
          <DockIcon>
            <Music />
          </DockIcon>
          <DockLabel>Music</DockLabel>
        </DockItem>
 
        <DockItem>
          <DockIcon>
            <Image />
          </DockIcon>
          <DockLabel>Photos</DockLabel>
        </DockItem>
 
        <DockSeparator />
 
        <DockItem>
          <DockIcon>
            <Store />
          </DockIcon>
          <DockLabel>App Store</DockLabel>
        </DockItem>
 
        <DockItem>
          <DockIcon>
            <Calculator />
          </DockIcon>
          <DockLabel>Calculator</DockLabel>
        </DockItem>
 
        <DockItem>
          <DockIcon>
            <Clock />
          </DockIcon>
          <DockLabel>Clock</DockLabel>
        </DockItem>
 
        <DockItem>
          <DockIcon>
            <Notebook />
          </DockIcon>
          <DockLabel>Notes</DockLabel>
        </DockItem>
 
        <DockItem>
          <DockIcon>
            <Terminal />
          </DockIcon>
          <DockLabel>Terminal</DockLabel>
        </DockItem>
 
        <DockItem>
          <DockIcon>
            <Settings />
          </DockIcon>
          <DockLabel>System Settings</DockLabel>
        </DockItem>
 
        <DockSeparator />
 
        <DockItem>
          <DockIcon>
            <Camera />
          </DockIcon>
          <DockLabel>Camera</DockLabel>
        </DockItem>
 
        <DockItem>
          <DockIcon className="text-red-500">
            <Trash2 />
          </DockIcon>
          <DockLabel>Trash</DockLabel>
        </DockItem>
      </Dock>
    </div>
  );
}

Social Media

import {
    Dock,
    DockIcon,
    DockItem,
    DockLabel,
} from "@/components/ui/dock";
import {
    Facebook,
    Github,
    Instagram,
    Linkedin,
    Twitter,
    Youtube,
} from "lucide-react";
 
export function DockSocialDemo() {
  return (
    <div className="flex h-32 items-end justify-center">
      <Dock
        magnification={60}
        distance={100}
        className="bg-transparent border-none shadow-none"
      >
        <DockItem className="bg-black/5 dark:bg-white/10">
          <DockIcon>
            <Github />
          </DockIcon>
          <DockLabel>GitHub</DockLabel>
        </DockItem>
 
        <DockItem className="bg-blue-500/10 text-blue-500">
          <DockIcon>
            <Twitter />
          </DockIcon>
          <DockLabel>Twitter</DockLabel>
        </DockItem>
 
        <DockItem className="bg-pink-500/10 text-pink-500">
          <DockIcon>
            <Instagram />
          </DockIcon>
          <DockLabel>Instagram</DockLabel>
        </DockItem>
 
        <DockItem className="bg-blue-700/10 text-blue-700">
          <DockIcon>
            <Linkedin />
          </DockIcon>
          <DockLabel>LinkedIn</DockLabel>
        </DockItem>
 
        <DockItem className="bg-blue-600/10 text-blue-600">
          <DockIcon>
            <Facebook />
          </DockIcon>
          <DockLabel>Facebook</DockLabel>
        </DockItem>
 
        <DockItem className="bg-red-600/10 text-red-600">
          <DockIcon>
            <Youtube />
          </DockIcon>
          <DockLabel>YouTube</DockLabel>
        </DockItem>
      </Dock>
    </div>
  );
}

Installation

CLI

npx shadcn@latest add "https://jolyui.dev/r/dock"

Manual

Install the following dependencies:

npm install motion lucide-react

Copy and paste the following code into your project. component/ui/dock.tsx

import { motion, useMotionValue, useSpring, useTransform } from "motion/react";
import * as React from "react";
import { cn } from "@/lib/utils";
 
interface DockProps {
  /** Distance threshold for magnification effect */
  magnification?: number;
  /** Maximum scale factor when hovered */
  maxScale?: number;
  /** Base size of dock items in pixels */
  iconSize?: number;
  /** Distance from mouse to apply magnification */
  distance?: number;
  className?: string;
  children?: React.ReactNode;
}
 
const DockContext = React.createContext<{
  mouseX: ReturnType<typeof useMotionValue<number>>;
  magnification: number;
  maxScale: number;
  iconSize: number;
  distance: number;
} | null>(null);
 
const Dock = React.forwardRef<HTMLDivElement, DockProps>(
  (
    {
      className,
      children,
      magnification = 60,
      maxScale = 1.5,
      iconSize = 48,
      distance = 140,
    },
    ref,
  ) => {
    const mouseX = useMotionValue(Infinity);
 
    return (
      <DockContext.Provider
        value={{ mouseX, magnification, maxScale, iconSize, distance }}
      >
        <motion.div
          ref={ref}
          onMouseMove={(e) => mouseX.set(e.pageX)}
          onMouseLeave={() => mouseX.set(Infinity)}
          className={cn(
            "dock mx-auto flex h-16 items-end gap-2 rounded-2xl border bg-background/80 px-3 pb-2 backdrop-blur-md",
            "shadow-foreground/5 shadow-lg",
            className,
          )}
        >
          {children}
        </motion.div>
      </DockContext.Provider>
    );
  },
);
Dock.displayName = "Dock";
 
interface DockItemProps {
  className?: string;
  children?: React.ReactNode;
  onClick?: () => void;
}
 
const DockItem = React.forwardRef<HTMLDivElement, DockItemProps>(
  ({ className, children, onClick }, _ref) => {
    const context = React.useContext(DockContext);
    const itemRef = React.useRef<HTMLDivElement>(null);
 
    if (!context) {
      throw new Error("DockItem must be used within a Dock");
    }
 
    const { mouseX, maxScale, iconSize, distance } = context;
 
    const distanceCalc = useTransform(mouseX, (val: number) => {
      const bounds = itemRef.current?.getBoundingClientRect() ?? {
        x: 0,
        width: 0,
      };
      return val - bounds.x - bounds.width / 2;
    });
 
    const widthSync = useTransform(
      distanceCalc,
      [-distance, 0, distance],
      [iconSize, iconSize * maxScale, iconSize],
    );
 
    const width = useSpring(widthSync, {
      mass: 0.1,
      stiffness: 150,
      damping: 12,
    });
 
    return (
      <motion.div
        ref={itemRef}
        style={{ width, height: width }}
        onClick={onClick}
        className={cn(
          "dock-item group relative flex aspect-square cursor-pointer items-center justify-center rounded-xl bg-muted transition-colors hover:bg-muted/80",
          className,
        )}
      >
        {children}
      </motion.div>
    );
  },
);
DockItem.displayName = "DockItem";
 
interface DockIconProps {
  className?: string;
  children?: React.ReactNode;
}
 
const DockIcon = React.forwardRef<HTMLDivElement, DockIconProps>(
  ({ className, children }, ref) => {
    return (
      <div
        ref={ref}
        className={cn(
          "dock-icon flex h-full w-full items-center justify-center text-foreground",
          "[&>svg]:h-1/2 [&>svg]:w-1/2",
          className,
        )}
      >
        {children}
      </div>
    );
  },
);
DockIcon.displayName = "DockIcon";
 
interface DockLabelProps {
  className?: string;
  children?: React.ReactNode;
}
 
const DockLabel = React.forwardRef<HTMLDivElement, DockLabelProps>(
  ({ className, children }, ref) => {
    return (
      <div
        ref={ref}
        className={cn(
          "dock-label -top-9 -translate-x-1/2 pointer-events-none absolute left-1/2 whitespace-nowrap rounded-md bg-foreground px-2 py-1 text-background text-xs opacity-0 transition-opacity group-hover:opacity-100",
          className,
        )}
      >
        {children}
        {/* Tooltip arrow */}
        <div className="-bottom-1 -translate-x-1/2 absolute left-1/2 h-2 w-2 rotate-45 bg-foreground" />
      </div>
    );
  },
);
DockLabel.displayName = "DockLabel";
 
interface DockSeparatorProps {
  className?: string;
}
 
const DockSeparator = React.forwardRef<HTMLDivElement, DockSeparatorProps>(
  ({ className }, ref) => {
    return (
      <div
        ref={ref}
        className={cn(
          "dock-separator mx-1 h-10 w-px self-center bg-border",
          className,
        )}
      />
    );
  },
);
DockSeparator.displayName = "DockSeparator";
 
export {
  Dock,
  DockIcon,
  DockItem,
  DockLabel,
  DockSeparator,
  type DockIconProps,
  type DockItemProps,
  type DockLabelProps,
  type DockProps,
  type DockSeparatorProps,
};

Usage

import { Dock, DockIcon, DockItem, DockLabel } from "@/components/ui/dock";

function MyDock() {
  return (
    <Dock>
      <DockItem>
        <DockIcon>
          <HomeIcon />
        </DockIcon>
        <DockLabel>Home</DockLabel>
      </DockItem>
      {/* More items... */}
    </Dock>
  );
}

API Reference

Dock

Prop

Type

DockItem

Prop

Type

DockIcon

Prop

Type

DockLabel

Prop

Type

DockSeparator

Prop

Type

Notes

  • The dock automatically applies magnification effects when hovering near items
  • Use DockSeparator to group related items
  • Icons are automatically sized and centered within DockIcon
  • Labels appear as tooltips on hover
  • The component uses Framer Motion for smooth animations

How is this guide?

On this page