Dock
A macOS-style magnifying dock — icons scale smoothly based on their horizontal distance from the cursor, with labels floating up on hover.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add dock.enDock is a React macOS-style dock (magnification dock) component built with Tailwind CSS and Motion. As the mouse approaches, icons scale up smoothly with a spring based on horizontal distance, and a label pops up above an icon on hover or keyboard focus.
Playground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<Dock/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
When to use
- A fixed bottom navigation bar on a portfolio or personal site, holding social links and key pages.
- Toolbars in editor or whiteboard apps, such as pen, select, text and export.
- A desktop shortcut bar on a mobile-first site; on touch it stays a row of regular-sized buttons.
- Not a good fit: main menus with more than about ten items or ones that need always-visible text labels; a regular navbar or sidebar is easier to scan.
Installation
npx webberui@latest add https://webberui.com/r/dock.en.jsonInstalling 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>Examples
Fixed to the bottom of the screen
Add positioning classes to className to pin the dock to the bottom center of the viewport.
import { Dock, DockItem } from "@/components/ui/dock";
import { Home, FolderOpen, Mail } from "lucide-react";
export function BottomDock() {
return (
<Dock className="fixed bottom-6 left-1/2 z-50 -translate-x-1/2">
<DockItem label="Home" href="/">
<Home />
</DockItem>
<DockItem label="Projects" href="/projects">
<FolderOpen />
</DockItem>
<DockItem label="Contact" href="mailto:hello@example.com">
<Mail />
</DockItem>
</Dock>
);
}Client-side navigation with Next.js
href renders a plain <a>. To use Next.js client-side routing, leave out href and call router.push in onClick instead.
"use client";
import { useRouter } from "next/navigation";
import { Dock, DockItem } from "@/components/ui/dock";
import { LayoutDashboard, Settings } from "lucide-react";
export function AppDock() {
const router = useRouter();
return (
<Dock>
<DockItem label="Overview" onClick={() => router.push("/dashboard")}>
<LayoutDashboard />
</DockItem>
<DockItem label="Settings" onClick={() => router.push("/settings")}>
<Settings />
</DockItem>
</Dock>
);
}Tune magnification and range
baseSize is the resting size, magnification the maximum size and distance the horizontal radius the cursor affects, all in px.
<Dock baseSize={48} magnification={80} distance={180}>
{/* DockItem… */}
</Dock>Props
Dock
| Prop | Type | Default | Description |
|---|---|---|---|
magnification | number | 64 | Icon size when the cursor is closest (px) |
baseSize | number | 40 | Icon size at rest (px) |
distance | number | 140 | Radius of the magnification effect (px): icons only scale up when the cursor is within this horizontal distance of their center |
className | string | — | Appended to the dock container's className |
children | React.ReactNode | — | Put DockItem children directly inside |
DockItem
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | — | Icon label: shown above on hover or keyboard focus, and used as the aria-label |
onClick | React.MouseEventHandler | — | Click event (works for both the button and the link) |
href | string | — | When provided, renders as an <a> link; otherwise a <button> |
children | React.ReactNode | — | Icon content: any SVG or element; it automatically fills the icon area and scales proportionally with the magnification |
className | string | — | Appended 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 toInfinitywhen the pointer leaves; everyDockItemusesuseTransformto map "horizontal distance from the cursor" onto a size range, then smooths it withuseSpring, producing the classic macOS magnification wave - The dock itself never jumps: the container height is fixed at
baseSizeplus 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 thearia-label, and the card itself is hidden from assistive technology so nothing is read twice - Keyboard accessibility:
DockItemis a native<button>(an<a>whenhrefis present), so Tab focuses them one by one; onfocus-visibleit magnifies tomagnificationand 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
childrenautomatically fills the icon area
FAQ
Does it work with the Next.js App Router?
Yes. The file starts with "use client", so import it from a Server Component page as usual. If you navigate with useRouter, the component that calls it must be a Client Component too.
Do I have to use lucide-react?
No. The component itself depends on no icon library, and any SVG you put in a DockItem's children fills the icon area and scales with it. The Usage example uses lucide-react, so install it separately if you copy that code.
Does it magnify on phones?
No. Magnification only responds to mouse pointer events; on touch it is a row of equal-sized buttons and taps work as usual.
Can keyboard and screen reader users operate it?
DockItem is a native <button>, or an <a> when href is set, so Tab moves through the items, and keyboard focus magnifies the item and shows its label. label also serves as the aria-label, and the floating label card is hidden from assistive technology so it is not read twice.
What happens with reduced motion?
Magnification and the label slide animation are turned off. The DOM structure stays the same and links and buttons work normally.
Labeled Mode Switch
A two-state text switch (e.g. Ask | Auto) with a glassy spring-driven thumb and labels that cross-fade with the thumb position — for operating-mode toggles.
Dynamic Island Nav
A Dynamic Island-style pill navigation — the selected pill slides and morphs between items, the selected item's name expands from 0, and one more click morphs the island downward into a panel.