Skip to content

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/motion

For 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
}

Reference

Propsitems, value, onValueChange… (8)

Props

Dock props
PropType
itemsRequiredDockItem[]–
valuestring

The active item's id; it gets the indicator dot.

–
onValueChange(id: string) => void–
sizenumber

Resting 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
magnificationnumber

Peak scale under the pointer.

default 1.6

1.6
distancenumber

How far the magnification reaches, in px from the pointer.

default 140

140
labelstring

Accessible name of the toolbar.

default "Dock"

"Dock"
classNamestring–
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

Motion tokens
MomentTokens
Magnificationpointer position and effect strength
spring.snappy

0.2s, bounce 0

0.2s, bounce 0
Active indicatortravels between items
spring.smooth

0.36s, bounce 0

0.36s, bounce 0
Tooltip in
duration.fastease.enter

140ms · cubic-bezier(0.16, 1, 0.3, 1)

140mscubic-bezier(0.16, 1, 0.3, 1)
Tooltip out
duration.instantease.exit

80ms · 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 toolbar with a roving tab stop, named by label (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.