Capsule Dock
A premium macOS‑style Dock with a glass‑morphic backdrop that adapts to light/dark themes. Icons respond to mouse proximity with magnetic attraction, scaling smoothly from 1.0→1.4 and lifting ~18px with subtle rotation.
Installation
Add this component to your project using the CLI:
npx vui-registry-cli-v1 add velocity-capsule-dockSource Code
// VelocityCapsuleDock.tsx
'use client'
import React from 'react'
import { motion, useMotionValue, useMotionTemplate, useTransform } from 'framer-motion'
import { Home, Zap, Shield, BarChart, Cpu, Terminal } from 'lucide-react'
import { useMagneticPhysics } from './use-magnetic-physics'
const NAV_ITEMS = [
{ label: 'Internal', icon: Home },
{ label: 'Power', icon: Zap },
{ label: 'Secure', icon: Shield },
{ label: 'Metrics', icon: BarChart },
{ label: 'Compute', icon: Cpu },
{ label: 'Console', icon: Terminal },
]
export const VelocityCapsuleDock = () => {
const mouseX = useMotionValue(Infinity)
return (
<nav className="relative w-full h-[64px] flex items-center justify-center">
<motion.div
onMouseMove={(e) => mouseX.set(e.pageX)}
onMouseLeave={() => mouseX.set(Infinity)}
className="relative flex items-center h-full gap-1 px-3 rounded-full border border-border bg-card/70 backdrop-blur-2xl shadow-[0_20px_40px_rgba(0,0,0,0.15)] overflow-hidden ring-1 ring-border/50"
>
{NAV_ITEMS.map((item, idx) => (
<DockItem key={idx} mouseX={mouseX} item={item} />
))}
</motion.div>
</nav>
)
}
const DockItem = ({ mouseX, item }: { mouseX: any; item: any }) => {
const { ref, width, scale, y, proximity } = useMagneticPhysics(mouseX)
const Icon = item.icon
// Fluent "Bloom" - Stays inside the container
const bloom = useMotionTemplate`radial-gradient(35px circle at 50% 50%, rgba(255,255,255,${useMotionTemplate`${proximity.get() * 0.15}`}), transparent)`
return (
<motion.div
ref={ref}
style={{ width }}
className="relative flex items-center justify-center h-full group"
>
{/* Tooltip - Positioned above the dock bounds */}
<motion.span
style={{
opacity: proximity,
y: -42,
scale: useMotionTemplate`${0.9 + proximity.get() * 0.1}`,
}}
className="absolute px-2.5 py-1 rounded-md bg-neutral-900/80 text-white text-[10px] font-medium backdrop-blur-xl border border-white/10 pointer-events-none whitespace-nowrap z-[110]"
>
{item.label}
</motion.span>
{/* Internal Fluent Light Stage */}
<motion.div className="absolute inset-0 pointer-events-none" style={{ background: bloom }} />
{/* The Icon Container */}
<motion.div
style={{
scale,
y,
opacity: useTransform(proximity, [0, 1], [0.75, 1]),
}}
className="relative flex items-center justify-center aspect-square w-10 h-10 z-10"
>
<Icon
size={22}
className="text-foreground/70 group-hover:text-foreground transition-colors duration-200"
strokeWidth={1.5}
style={{ filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.2))' }}
/>
</motion.div>
{/* Active Indicator Dot */}
<motion.div
className="absolute bottom-1.5 w-1 h-[2px] bg-white/40 rounded-full"
style={{
opacity: useMotionTemplate`${proximity.get() * 0.8}`,
scaleX: useMotionTemplate`${0.6 + proximity.get() * 0.4}`,
}}
/>
</motion.div>
)
}
export default VelocityCapsuleDock
Source Code
"use client";
import { MotionValue, useSpring, useTransform } from "framer-motion";
import { useMemo, useRef } from "react";
const clamp = (v: number, min: number, max: number) => Math.min(max, Math.max(min, v));
export function useMagneticPhysics(mouseX: MotionValue<number>, radius: number = 200) {
const ref = useRef<HTMLDivElement>(null);
const distance = useTransform(mouseX, (val: number) => {
const bounds = ref.current?.getBoundingClientRect() ?? { left: 0, width: 0 };
return val - (bounds.left + bounds.width / 2);
});
const proximity = useTransform(distance, (d) => {
if (!isFinite(d)) return 0;
const p = 1 - Math.abs(d) / radius;
// Cubic ease-in for a "magnetic snap" feel
return Math.pow(clamp(p, 0, 1), 3);
});
const widthT = useTransform(proximity, [0, 1], [44, 72]);
const scaleT = useTransform(proximity, [0, 1], [1, 1.45]);
const liftT = useTransform(proximity, [0, 1], [0, -10]);
// Spring configured for "High-End Damping" - no wobble, just smooth travel
const springCfg = { mass: 0.1, stiffness: 260, damping: 24 };
const width = useSpring(widthT, springCfg);
const scale = useSpring(scaleT, springCfg);
const y = useSpring(liftT, springCfg);
return { ref, width, scale, y, proximity };
}Dependencies
framer-motion: latestlucide-react: latest
Props
Component property reference.
| Name | Type | Default | Description |
|---|---|---|---|
| items | Array<{ label: string; icon: LucideIcon }> | NAV_ITEMS | Icons and labels to render in the dock. Each item includes a label and a Lucide icon component. |
| radius | number | 140 | Proximity radius for magnetic attraction. Larger values create a wider area of influence. |
| className | string | undefined | Additional classes for the dock container to customize spacing or shadows. |
| onSelect | (index: number, item: { label: string; icon: LucideIcon }) => void | undefined | Callback fired when an icon is clicked. Receives the item index and item payload. |
| fixed | boolean | true | If true, positions the dock fixed at the bottom center. Set false to mount inline for previews. |
Most components here are inspired by outstanding libraries and creators in the ecosystem. I don’t claim to be the original author — this is my space for learning, rebuilding, and understanding great work at a deeper level.
I’m still a student of the craft, constantly studying the best and translating what I learn through my own perspective. Every piece reflects curiosity, respect for the community, and small creative touches that feel true to me.
I’ve done my best to credit inspirations properly. If anything is missing or inaccurate, I truly appreciate a message so it can be corrected with care.

