WebberUI

Dock

A macOS-style magnifying dock — icons scale smoothly based on their horizontal distance from the cursor, with labels floating up on hover.

Loading preview…
npx shadcn@latest add https://webberui.com/r/dock.json

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

64
40
140
<Dock />

Installation

npx shadcn@latest add https://webberui.com/r/dock.json

Installing also writes the --wb-duration-fast CSS variable into your global stylesheet.

Usage

import { Dock, DockItem } from "@/components/ui/dock";
import { Home, Search, Settings } from "lucide-react";

<Dock>
  <DockItem label="Home" href="/">
    <Home />
  </DockItem>
  <DockItem label="Search" onClick={() => console.log("search")}>
    <Search />
  </DockItem>
  <DockItem label="Settings" href="/settings">
    <Settings />
  </DockItem>
</Dock>

Props

Dock

PropTypeDefaultDescription
magnificationnumber64Icon size when the cursor is closest (px)
baseSizenumber40Icon size at rest (px)
distancenumber140Radius of the magnification effect (px): icons only scale up when the cursor is within this horizontal distance of their center
classNamestringAppended to the dock container's className
childrenReact.ReactNodePut DockItem children directly inside

DockItem

PropTypeDefaultDescription
labelstringIcon label: shown above on hover or keyboard focus, and used as the aria-label
onClickReact.MouseEventHandlerClick event (works for both the button and the link)
hrefstringWhen provided, renders as an <a> link; otherwise a <button>
childrenReact.ReactNodeIcon content: any SVG or element; it automatically fills the icon area and scales proportionally with the magnification
classNamestringAppended to the individual icon button's className

How it works

  • Shared mouseX: the container's pointermove updates a shared cursor X coordinate (a MotionValue), set to Infinity when the pointer leaves; every DockItem uses useTransform to map "horizontal distance from the cursor" onto a size range, then smooths it with useSpring, producing the classic macOS magnification wave
  • The dock itself never jumps: the container height is fixed at baseSize plus padding with the icons bottom-aligned, so magnification overflows upward
  • Tooltip labels: hovering a single icon floats a small card above it via AnimatePresence; the label is also the aria-label, and the card itself is hidden from assistive technology so nothing is read twice
  • Keyboard accessibility: DockItem is a native <button> (an <a> when href is present), so Tab focuses them one by one; on focus-visible it magnifies to magnification and shows the label just the same
  • Automatic touch fallback: it only responds to mouse pointer events (pointerType === "mouse"), so on touch it is simply a row of equally sized ordinary buttons
  • When the user has "reduce motion" enabled at the system level, the magnification and label displacement animations are disabled; the DOM structure is completely unchanged and functionality is unaffected
  • The component itself does not depend on any icon library — whatever SVG you put in children automatically fills the icon area

On this page