Dock
dock ui macOS-style dock menu with magnification on hover. Items expand as the cursor approaches using a cosine bell curve. Supports an items array (icon + label + handler), magnification scale, base size, tooltips on hover, click handlers, and an active state indicator.
Also available for Vue ->Installation
$ pnpm dlx shadcn@latest add https://uipkge.dev/r/react/dock.json $ npx shadcn@latest add https://uipkge.dev/r/react/dock.json $ yarn dlx shadcn@latest add https://uipkge.dev/r/react/dock.json $ bunx shadcn@latest add https://uipkge.dev/r/react/dock.json Named registry:
npx shadcn@latest add @uipkge-react/dock Installs to: components/ui/dock/ Examples
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
items Dock items. | DockItem[] | — | required |
baseSize Base icon size in pixels. Default 48. | number | — | optional |
magnification Peak magnification scale as the cursor hovers directly over an item. Default 1.6. | number | — | optional |
distance Pixel radius within which items magnify. Default 120. | number | — | optional |
orientation Orientation. Only 'horizontal' (bottom dock) is supported. | 'horizontal' | — | optional |
showTooltips Show the tooltip label on hover. Default true. | boolean | — | optional |
Schema
Type aliases from this item's source — use them to shape the data you pass in.
DockItem interface DockItem {
/** Unique id for the item. */
id: string
/** Lucide icon component to render. */
icon: LucideIcon
/** Label shown in the tooltip on hover. */
label: string
/** Click handler. */
handler?: () => void
/** Whether this item is the active one. */
active?: boolean
} npm dependencies
Files installed (2)
-
components/ui/dock/Dock.tsx 5.1 kB
import * as React from 'react' import type { LucideIcon } from 'lucide-react' import { cn } from '@/lib/utils' export interface DockItem { /** Unique id for the item. */ id: string /** Lucide icon component to render. */ icon: LucideIcon /** Label shown in the tooltip on hover. */ label: string /** Click handler. */ handler?: () => void /** Whether this item is the active one. */ active?: boolean } export interface DockProps extends React.HTMLAttributes<HTMLDivElement> { /** Dock items. */ items: DockItem[] /** Base icon size in pixels. Default 48. */ baseSize?: number /** Peak magnification scale as the cursor hovers directly over an item. Default 1.6. */ magnification?: number /** Pixel radius within which items magnify. Default 120. */ distance?: number /** Orientation. Only 'horizontal' (bottom dock) is supported. */ orientation?: 'horizontal' /** Show the tooltip label on hover. Default true. */ showTooltips?: boolean } const Dock = React.forwardRef<HTMLDivElement, DockProps>( ( { items, baseSize = 48, magnification = 1.6, distance = 120, orientation = 'horizontal', showTooltips = true, className, ...props }, ref, ) => { const mouseXRef = React.useRef<number | null>(null) const [mouseX, setMouseX] = React.useState<number | null>(null) const [hoveredId, setHoveredId] = React.useState<string | null>(null) const itemRefs = React.useRef<(HTMLDivElement | null)[]>([]) function onMove(e: React.MouseEvent<HTMLDivElement>) { mouseXRef.current = e.clientX setMouseX(e.clientX) } function onLeave() { mouseXRef.current = null setMouseX(null) setHoveredId(null) } function sizeFor(index: number): number { if (mouseXRef.current === null) return baseSize const el = itemRefs.current[index] if (!el) return baseSize const rect = el.getBoundingClientRect() const center = rect.left + rect.width / 2 const dist = Math.abs(mouseXRef.current - center) if (dist > distance) return baseSize // Cosine bell curve so magnification falls off smoothly. const t = 1 - dist / distance const scale = 1 + (magnification - 1) * t return baseSize * scale } const sizes = items.map((_, i) => sizeFor(i)) return ( <div ref={ref} data-uipkge="" data-slot="dock" data-orientation={orientation} className={cn( 'border-border/60 bg-background/60 flex items-end justify-center gap-3 rounded-2xl border px-3 py-2 backdrop-blur-md', className, )} onMouseMove={onMove} onMouseLeave={onLeave} {...props} > {items.map((item, index) => { const Icon = item.icon const size = sizes[index] ?? baseSize return ( <div key={item.id} ref={(el) => { itemRefs.current[index] = el }} data-slot="dock-item" data-active={item.active ? '' : undefined} role="button" tabIndex={0} aria-label={item.label} aria-current={item.active ? 'true' : undefined} className="group focus-visible:ring-ring/50 relative flex shrink-0 cursor-pointer items-end justify-center rounded-xl outline-none focus-visible:ring-[3px]" onMouseEnter={() => setHoveredId(item.id)} onMouseLeave={() => setHoveredId(null)} onClick={() => item.handler?.()} onKeyDown={(e) => { if (e.key === 'Enter') { item.handler?.() } else if (e.key === ' ') { e.preventDefault() item.handler?.() } }} > {/* Tooltip */} {showTooltips && hoveredId === item.id ? ( <span className="border-border bg-popover text-popover-foreground pointer-events-none absolute -top-9 left-1/2 -translate-x-1/2 rounded-md border px-2 py-1 text-xs whitespace-nowrap shadow-md"> {item.label} </span> ) : null} {/* Icon tile */} <span className={cn( 'flex items-center justify-center rounded-xl border transition-[width,height] duration-100 ease-out will-change-[width,height]', item.active ? 'border-primary/40 bg-primary/10 text-primary' : 'border-border/50 bg-muted/40 text-foreground hover:bg-muted', )} style={{ width: `${size}px`, height: `${size}px` }} > <Icon style={{ width: `${size * 0.5}px`, height: `${size * 0.5}px` }} /> </span> {/* Active indicator dot */} {item.active ? ( <span className="bg-primary absolute -bottom-1.5 size-1 rounded-full" aria-hidden="true" /> ) : null} </div> ) })} </div> ) }, ) Dock.displayName = 'Dock' export { Dock } -
components/ui/dock/index.ts 0.1 kB
export { Dock, type DockProps, type DockItem } from './Dock'
Raw manifest: https://uipkge.dev/r/react/dock.json