- All Components
- Animated Arrow
- Animated Counter
- Animated Save Button
- Animated Tabs
- Animated List/Grid View
- Bento Grid
- Border Beam
- Button
- Animated Copy Button
- Dock
- Animated 3D Folder
- Cursor-Tracking Glow Card
- Inline Edit Animation
- Animated OTP Input
- Infinite Marquee
- Animated Password Input
- Interactive Pie Chart
- Reveal on Scroll
- Tweet Card
- Typewriter
- Interactive World Map
Installation
npx shadcn@latest add https://tentui.com/r/dock.json
Usage
Wrap a row of DockIcons in Dock. Each icon reads the shared cursor position from context and animates its own scale.
import { Home, Mail, Settings } from "lucide-react";
import { Dock, DockIcon } from "@/components/dock";
export function AppDock() {
return (
<Dock>
<DockIcon aria-label="Home">
<Home className="size-1/2" />
</DockIcon>
<DockIcon aria-label="Mail">
<Mail className="size-1/2" />
</DockIcon>
<DockIcon aria-label="Settings">
<Settings className="size-1/2" />
</DockIcon>
</Dock>
);
}The cursor position lives in a single useMotionValue on the dock. Each icon subscribes via useTransform, so React doesn't re-render on mousemove — Framer Motion writes the new size straight to the DOM.
Patterns
Tighter or looser magnification
Tune the resting size, peak size, and "feel radius" to fit your dock width. A smaller distance makes the effect feel sharp and local; a larger one makes it ripple across more icons.
<Dock baseSize={36} maxSize={56} distance={100}>{icons}</Dock>
<Dock baseSize={48} maxSize={96} distance={200}>{icons}</Dock>Routing
DockIcon is a <motion.button> — wrap with your router link or pass onClick. For Next.js, render a <Link> around each <DockIcon> instead of using onClick.
<Link href="/inbox">
<DockIcon aria-label="Inbox"><Mail className="size-1/2" /></DockIcon>
</Link>Custom spring
Pass a spring to fine-tune the bounce. Lower mass + higher stiffness = snappier; higher damping = less overshoot.
<Dock spring={{ mass: 0.05, stiffness: 200, damping: 14 }}>{icons}</Dock>Props
<Dock />
| Prop | Type | Default | Description |
|---|---|---|---|
baseSize | number | 44 | Resting icon size in pixels. |
maxSize | number | 72 | Icon size at the exact cursor position. |
distance | number | 140 | Pixel radius around the cursor where icons start to magnify. |
spring | SpringOptions | { mass: 0.1, stiffness: 150, damping: 12 } | Framer Motion spring config for the scale animation. |
className | string | — | Forwarded to the outer container. |
<DockIcon />
DockIcon is a <motion.button> — every native button prop (onClick, aria-label, disabled, …) passes through. Set aria-label so screen readers can announce each icon.
Accessibility
- Each icon is a real
<button>withfocus-visiblestyling and keyboard focus support. - The magnification effect is purely visual — it doesn't affect tab order, hit targets, or the accessibility tree.
- Provide a meaningful
aria-labelfor every icon since they have no visible text.