ComponentsChangelogPro

    Getting Started

    Animated Cards

    Components

    Backgrounds

    3d & Shaders

    Text Animations

    Buttons

    Docs
    Dock

    Dock

    An interactive icon dock that smoothly scales icons on hover, providing a responsive and engaging visual effect.

    Loading...

    Installation

    Install dependencies

    npm install

    npm install clsx tailwind-merge

    Add util file

    lib/utils.ts

    import { ClassValue, clsx } from "clsx";
    import { twMerge } from "tailwind-merge";
     
    export function cn(...inputs: ClassValue[]) {
      return twMerge(clsx(inputs));
    }

    Copy the source code

    dock.tsx

    "use client";
     
    import * as React from "react";
    import { cn, scaleValue } from "@/lib/utils";
    import { useRef } from "react";
     
    interface SocialIconProps {
      className?: string;
      src: string;
      href: string;
      name: string;
      handleIconHover?: (e: React.MouseEvent<HTMLLIElement>) => void;
      iconSize: number;
    }
     
    export const SocialIcon = React.forwardRef<HTMLLIElement, SocialIconProps>(
      ({ className, src, href, name, handleIconHover, iconSize }, ref) => {
        return (
          <>
            <style jsx>
              {`
                .icon:hover + .icon {
                  width: calc(
                    var(--icon-size) * 1.33 + var(--dock-offset-right, 0px)
                  );
                  height: calc(
                    var(--icon-size) * 1.33 + var(--dock-offset-right, 0px)
                  );
                  margin-top: calc(
                    var(--icon-size) * -0.33 + var(--dock-offset-right, 0) * -1
                  );
                }
     
                .icon:hover + .icon + .icon {
                  width: calc(
                    var(--icon-size) * 1.17 + var(--dock-offset-right, 0px)
                  );
                  height: calc(
                    var(--icon-size) * 1.17 + var(--dock-offset-right, 0px)
                  );
                  margin-top: calc(
                    var(--icon-size) * -0.17 + var(--dock-offset-right, 0) * -1
                  );
                }
     
                .icon:has(+ .icon:hover) {
                  width: calc(
                    var(--icon-size) * 1.33 + var(--dock-offset-left, 0px)
                  );
                  height: calc(
                    var(--icon-size) * 1.33 + var(--dock-offset-left, 0px)
                  );
                  margin-top: calc(
                    var(--icon-size) * -0.33 + var(--dock-offset-left, 0) * -1
                  );
                }
     
                .icon:has(+ .icon + .icon:hover) {
                  width: calc(
                    var(--icon-size) * 1.17 + var(--dock-offset-left, 0px)
                  );
                  height: calc(
                    var(--icon-size) * 1.17 + var(--dock-offset-left, 0px)
                  );
                  margin-top: calc(
                    var(--icon-size) * -0.17 + var(--dock-offset-left, 0) * -1
                  );
                }
              `}
            </style>
            <li
              ref={ref}
              style={{
                transition:
                  "width, height, margin-top, cubic-bezier(0.25, 1, 0.5, 1) 150ms",
                "--icon-size": `${iconSize}px`,
              }}
              onMouseMove={handleIconHover}
              className={cn(
                `icon h-[var(--icon-size)] w-[var(--icon-size)] rounded-md px-[calc(var(--icon-size)*0.133)] group/li cursor-pointer hover:w-[calc(var(--icon-size)*1.5)] hover:h-[calc(var(--icon-size)*1.5)] hover:-mt-[calc(var(--icon-size)/2)]`,
                className
              )}
            >
              <a href={href} className="relative">
                <span
                  className={`text-xs absolute top-[-30px] border border-zinc-200 dark:border-zinc-800 text-black dark:text-white rounded-md bg-gradient-to-t from-zinc-100/50 dark:from-zinc-900 p-1 px-2 left-1/2 -translate-x-1/2 opacity-0 transition-opacity duration-200 group-hover/li:opacity-100 `}
                >
                  {name}
                </span>
                <img
                  src={src}
                  alt={name}
                  className="rounded-[inherit] h-full w-full"
                />
              </a>
            </li>
          </>
        );
      }
    );
     
    SocialIcon.displayName = "SocialIcon";
     
    interface SocialDockProps {
      className?: string;
      children: React.ReactNode;
      maxAdditionalSize?: number;
      iconSize?: number;
    }
     
    export const SocialDock = React.forwardRef<HTMLDivElement, SocialDockProps>(
      ({ className, children, maxAdditionalSize = 5, iconSize = 40 }, ref) => {
        const dockRef = useRef<HTMLDivElement | null>(null);
     
        const handleIconHover = (e: React.MouseEvent<HTMLLIElement>) => {
          if (!dockRef.current) return;
          const mousePos = e.clientX;
          const iconPosLeft = e.currentTarget.getBoundingClientRect().left;
          const iconWidth = e.currentTarget.getBoundingClientRect().width;
     
          const cursorDistance = (mousePos - iconPosLeft) / iconWidth;
          const offsetPixels = scaleValue(
            cursorDistance,
            [0, 1],
            [maxAdditionalSize * -1, maxAdditionalSize]
          );
     
          dockRef.current.style.setProperty(
            "--dock-offset-left",
            `${offsetPixels * -1}px`
          );
     
          dockRef.current.style.setProperty(
            "--dock-offset-right",
            `${offsetPixels}px`
          );
        };
     
        return (
          <nav ref={dockRef} className="">
            <ul
              className={cn(
                "flex items-center bg-gradient-to-t from-zinc-100/50 dark:from-zinc-900 shadow-sm dark:shadow-none p-2 rounded-2xl border border-zinc-200 dark:border-zinc-800",
                className
              )}
            >
              {React.Children.map(children, (child) =>
                React.isValidElement(child)
                  ? React.cloneElement(child, { handleIconHover, iconSize })
                  : child
              )}
            </ul>
          </nav>
        );
      }
    );
     
    SocialDock.displayName = "SocialDock";

    Props

    Dock props

    PropTypeDescriptionDefault
    classNamestringAdditional CSS classes to apply to the Dock container-
    childrenReact.ReactNodeThe Dock icons to be displayed-
    maxAdditionalSizenumberThe maximum additional size in pixels for icons when hovered, to create the scaling effect5
    iconSizenumberThe base size of each icon (in pixels)50

    DockIcon props

    PropTypeDescriptionDefault
    classNamestringAdditional CSS classes to apply to the DockIcon-
    childrenReact.ReactNodeThe content inside the DockIcon, typically an SVG or icon element-
    hrefstringURL that the icon will link to when clicked-
    namestringThe name displayed on hover-
    iconSizenumberThe base size of the icon in pixels-