Dock
ProA 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#
The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.
Have a license? Unlock it here
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.
Anatomy#
import {
Dock,
DockItem,
DockGroup,
DockLabel,
DockSeparator,
DockExpander,
} from "@/components/ui/dock"
<Dock>
<DockItem />
<DockSeparator />
<DockGroup>
<DockLabel />
<DockItem />
</DockGroup>
<DockExpander />
</Dock>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 roving-focus root, Radix Toolbar.Root (a <div role="toolbar">); forwards all its props except orientation, which follows the shape. Every part accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| aria-label | string | "Dock" | Accessible name of the toolbar. |
| variant | "floating" | "solid" | "floating" | The page's popover surface, or an inverted slab for a dock over a photo, canvas or video. |
| size | "sm" | "md" | "lg" | "md" | The rail only: 24px, 32px or 40px icon buttons. The panel ignores it. |
| density | "comfortable" | "compact" | "compact" | The panel only: 36px or 32px rows, like DropdownMenu. Falls back to the nearest DensityProvider. |
| expanded | boolean | - | Controlled shape: true is the panel, false the rail. |
| defaultExpanded | boolean | false | Shape on mount when uncontrolled. |
| onExpandedChange | (expanded: boolean) => void | - | Called when the dock expands or collapses. |
| trigger | "click" | "hover" | "click" | What opens the panel: a press on DockExpander, or the pointer landing on it (then it collapses shortly after the pointer leaves). |
| closeOnSelect | boolean | true | Collapse back to the rail after a DockItem is chosen. |
| tooltipPlacement | "top" | "bottom" | "left" | "right" | "top" | Where the rail icons' tooltips open; also takes Tippy's -start, -end and auto placements. |
| static | boolean | false | Kept for compatibility; controls no longer scale on press. |
| loop | boolean | true | Arrow keys wrap from the last control to the first. |
| dir | "ltr" | "rtl" | - | Reading direction for arrow-key movement. |
| Data attribute | Description |
|---|---|
| data-expanded | Present while the dock is in its panel shape. |
| data-ready | Present once the box has measured itself; the morph transition is armed only then. |
| data-orientation | "horizontal" in the rail, "vertical" in the panel. |
| CSS variable | Description |
|---|---|
| --surface | Set to var(--popover) (floating) or var(--foreground) (solid), read by nested controls. |
DockItem
A destination or action, Radix Toolbar.Button: an icon with the label as its tooltip in the rail, a labelled row in the panel. Forwards no other native props.
| Prop | Type | Default | Description |
|---|---|---|---|
| label* | string | - | The tooltip and aria-label in the rail, the row text in the panel. |
| icon | React.ReactNode | - | The glyph; sized by size in the rail and to 20px in the panel unless it carries a size-* class. |
| shortcut | string | - | Display-only key chip at the end of the row; rides in the tooltip while collapsed. |
| active | boolean | false | Marks the current destination and sets aria-current="page". |
| disabled | boolean | false | Dims the control and removes it from the roving focus. |
| onSelect | (event: React.MouseEvent<HTMLElement>) => void | - | Called on press, before closeOnSelect collapses the dock. |
| asChild | boolean | false | Render into your single child (a Link, an <a>); the dock still fills it with the icon and label. |
| Data attribute | Description |
|---|---|
| data-active | Present on the current destination. |
| data-orientation | The dock's current arrow-key axis. |
DockGroup
A drill-down layer: an icon in the rail, a row with a caret in the panel. Opening it swaps the panel for a Back row plus its children; nest groups to go deeper.
| Prop | Type | Default | Description |
|---|---|---|---|
| label* | string | - | Tooltip in the rail, row text in the panel. |
| icon | React.ReactNode | - | The glyph. |
| value | string | - | Identity in the open path; defaults to label. |
| backLabel | string | "Back" | Label of the row that returns to the parent layer. |
| disabled | boolean | false | Dims the control and removes it from the roving focus. |
| children | React.ReactNode | - | The rows of this group's layer. |
DockLabel
A muted caption over a run of rows, rendered only in the panel. Forwards all native <div> props.
DockSeparator
An axis-aware hairline, Radix Toolbar.Separator: a vertical tick in the rail, a horizontal rule in the panel. Forwards all its props.
| Data attribute | Description |
|---|---|
| data-orientation | The separator's own axis, opposite the dock's. |
DockExpander
The rail's last control, which turns the rail into the panel; it unmounts once expanded.
| Prop | Type | Default | Description |
|---|---|---|---|
| label | string | "Menu" | Accessible name and tooltip. |
| children | React.ReactNode | - | Replaces the default list glyph. |