Dock
Navigate with a row of icons that magnify under the pointer or keyboard focus.
import { Dock, DockItem } from '@youj/motion/interactions'
Installation & usage
npm i @youj/motionFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
Use it for a short row of destinations people return to, where the row itself is a signature of the product: a launcher, a portfolio's sections, a creative tool's modes. Keep it to about five to ten items that are recognizable by icon alone. Don't use it for primary app navigation with text labels, or for more items than fit on a phone. A Sidebar or Tabs serve those better.
import { useState } from 'react'
import { HomeIcon, LayersIcon, MailIcon } from '@youj/icons'
import { Dock, type DockItem } from '@youj/motion/interactions'
const items: DockItem[] = [
{ id: 'home', label: 'Home', icon: <HomeIcon /> },
{ id: 'mail', label: 'Mail', icon: <MailIcon /> },
{ id: 'docs', label: 'Docs', icon: <LayersIcon />, href: '/docs' },
]
export function AppDock() {
const [active, setActive] = useState('home')
return <Dock items={items} value={active} onValueChange={setActive} />
}Items render as buttons, or as links when they have an href. The item whose id matches value gets a dot, which travels to the next active item.
interface DockItem {
id: string
label: string // accessible name and tooltip
icon: ReactNode
href?: string // render as a link
}Description
A row of eight app icons that magnify under the pointer or keyboard focus, with a dot on the open app.
A dock with its lens shaped by size, magnification and distance.
One spring smooths the pointer's position and a second fades the effect in and out. Each icon's scale and offset is a pure function of those two values, so neighbors can never disagree about where they are, and the point under the cursor stays under the cursor while the row grows around it. Icons grow up from their baseline, and the backplate widens with them through a clip-path inset. Nothing triggers layout.
Shape the lens with three props: size is the resting icon size, magnification the peak scale under the pointer, and distance how far, in pixels, the effect reaches. A dense toolbar wants a small, quiet lens, and an app launcher can afford the default.
Code
dock-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propsitems, value, onValueChange… (8)
Props
| Prop | Type | |
|---|---|---|
itemsRequired | DockItem[] | – |
value | stringThe active item's id; it gets the indicator dot. | – |
onValueChange | (id: string) => void | – |
size | numberResting icon size in px. In a container too narrow for the row, the icons shrink to fit it, to no less than 28. default 48 | 48 |
magnification | numberPeak scale under the pointer. default 1.6 | 1.6 |
distance | numberHow far the magnification reaches, in px from the pointer. default 140 | 140 |
label | stringAccessible name of the toolbar. default "Dock" | "Dock" |
className | string | – |
KeyboardThe dock is a single tab stop (role=toolbar)
Keyboard
The dock is a single tab stop (role="toolbar"). The arrow keys move focus, Home and End jump to the ends, and Enter or Space activates. Keyboard focus magnifies exactly like the pointer, and shows the same tooltip. Tab into the preview and try it.
MotionMagnification, active indicator, tooltip in… · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
| Magnificationpointer position and effect strength | spring.snappy0.2s, bounce 0 | 0.2s, bounce 0 | |
| Active indicatortravels between items | spring.smooth0.36s, bounce 0 | 0.36s, bounce 0 | |
| Tooltip in | duration.fastease.enter140ms · cubic-bezier(0.16, 1, 0.3, 1) | 140mscubic-bezier(0.16, 1, 0.3, 1) | |
| Tooltip out | duration.instantease.exit80ms · cubic-bezier(0.4, 0, 1, 1) | 80mscubic-bezier(0.4, 0, 1, 1) |
Reduced motion
Icons stay at their resting size. Tooltips and the active indicator change instantly.
Reduced motion
The lens is off: every icon stays at its resting size, under the pointer and under keyboard focus alike. Tooltips still appear on hover and focus, and the active dot still marks the current item, but both change instantly instead of traveling. The dock stays a complete, labeled toolbar.
AccessibilityKeyboard, ARIA
Accessibility
- It's a
toolbarwith a roving tab stop, named bylabel(default "Dock"). - Each item's accessible name is its
label. Tooltips are visual only, because the name is already on the button. - The active item carries
aria-current. - Touch input doesn't magnify: on a phone, the dock is a plain row of buttons.