Dock
A floating rail of icon controls that morphs, in place, into a labelled menu with drill-down layers. One box animates its own width, height and corner, so the rail becomes the menu instead of opening a second surface.
Installation#
Usage#
import {
Dock,
DockItem,
DockGroup,
DockLabel,
DockSeparator,
DockExpander,
} from "@/components/ui/dock"
<Dock aria-label="Workspace">
<DockItem icon={<SquaresFour weight="bold" />} label="Components" shortcut="⌘1" active />
<DockGroup icon={<Palette weight="bold" />} label="Appearance">
<DockLabel>Theme</DockLabel>
<DockItem icon={<Sun weight="bold" />} label="Light" />
</DockGroup>
<DockExpander />
</Dock>The label is a prop, not children, because the dock renders it twice: as the tooltip on the collapsed icon, and as the row's text in the panel. One source, two shapes.
Variants#
Floating is the default: the house's elevated popover surface, because a dock is chrome and chrome should inherit the page's theme rather than punch a slab of the opposite colour through it. Solid is the inverted slab, for a dock that has to hold its own over a photograph, a canvas or a video. It flips foreground and background rather than hardcoding black, so it reads dark on the light themes and light on the dark ones.
Sizes and density#
size sizes the rail, and only the rail. It is not the page's comfortable/compact knob, because a floating control bar that shrinks to form-compact becomes hard to hit. Every step is built on one identity, 4px padding + half the icon button = the rail's corner, so the buttons come out as true circles and concentric inside the pill at all three sizes. The 4px is the panel's own padding, so the rail and the menu it opens into read at one scale. The panel is a menu, so it follows density like every DropdownMenu on the page: the same 36px or 32px rows, row for row.
Layers#
A DockGroup is a drill-down. Opening it replaces the panel's contents with a “Back” row and its children, and the box resizes to fit: no second surface, no popup beside a popup. Groups nest, so a group inside a group is one layer deeper. Press Esc to step back one layer at a time.
Captions and shortcuts#
DockLabel sorts a long panel into topics, and shortcut puts the menu's gray key chip at the end of a row, exactly where DropdownMenuShortcut sits. Both live in the panel only: the rail has no room for a word, so while the dock is collapsed the shortcut rides in the tooltip beside the label instead.
Trigger#
trigger="click" is the default and the right pick on touch. trigger="hover" expands when the pointer lands on the expander and collapses shortly after it leaves the dock. The grace period is there so crossing a 2px gap on the way to a row never dismisses the thing you are reaching for.
States#
active marks the current destination in ink, not colour: a faint chip, a heavier label and a full-strength glyph among muted ones (it also sets aria-current="page"). disabled takes a control out of the roving focus, and DockSeparator is axis-aware: a vertical tick in the rail, a horizontal rule in the panel.
Links#
Pass asChild with your own element and the dock renders its icon and label inside it, so a nav dock can use next/link and keep client-side routing.
Controlled#
Drive the shape yourself with expanded / onExpandedChange, to open the dock from a keyboard shortcut, or to keep it open while a tour points at it.
API reference#
Dock
The morphing box and the roving-focus root (Radix Toolbar.Root, so it forwards dir, loop and the rest). Takes variant (floating · solid), size (sm · md · lg, the rail), density (comfortable · compact, the panel; defaults to the nearest DensityProvider), expanded / defaultExpanded / onExpandedChange, trigger (click · hover), closeOnSelect, tooltipPlacement, and static to drop the press-scale on the rail icons (panel rows never scale, like menu rows). The arrow-key axis follows the shape: horizontal in the rail, vertical in the panel.
DockItem
A destination or an action. Takes icon, label (required), shortcut, active, disabled, onSelect, and asChild to hand the control to a link. Collapsed it is an icon with the label as its tooltip; expanded it is a labelled row.
DockGroup
A drill-down layer. Takes icon, label, value (its identity in the open path, defaulting to the label), backLabel (default "Back") and disabled. Its children are the rows of its layer; pressing it from the rail expands the dock straight into that branch.
DockLabel · DockExpander · DockSeparator
DockLabel is a caption over a run of rows (panel only). DockExpander is the control that turns the rail into the panel (label, or your own children in place of the menu glyph); it exists only in the rail, because once the labels are on screen there is nothing left for it to reveal. DockSeparator is the axis-aware hairline (Radix Toolbar.Separator).