Ornament Toolbar
A visionOS-style ornament toolbar floating just outside the edge of a content panel: it shrinks to a pill while scrolling and expands to full controls when you stop, with Z-axis lift and a follow-along parallax.
npx shadcn@latest add https://webberui.com/r/ornament-toolbar.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<OrnamentToolbar />
Installation
npx shadcn@latest add https://webberui.com/r/ornament-toolbar.jsonOr, once registries are configured in components.json, install it as @webberui/ornament-toolbar.
Usage
OrnamentToolbar wraps a scrollable content panel (OrnamentContent) and a floating toolbar (Ornament). The toolbar docks just outside the panel's edge, shrinking to a pill while OrnamentContent scrolls and expanding when scrolling stops; hovering with the mouse or focusing from the keyboard also forces it open. Use OrnamentItem for each control, and insert an OrnamentSeparator between groups.
import {
Ornament,
OrnamentContent,
OrnamentItem,
OrnamentSeparator,
OrnamentToolbar,
} from "@/components/ui/ornament-toolbar";
import { Bookmark, Minus, Plus, Share2 } from "lucide-react";
export function Example() {
return (
<OrnamentToolbar side="bottom" className="h-[280px]">
<OrnamentContent className="p-5">{/* scrollable content */}</OrnamentContent>
<Ornament aria-label="Document toolbar">
<OrnamentItem icon={<Minus />} aria-label="Zoom out" />
<OrnamentItem icon={<Plus />} label="100%" aria-label="Zoom in" />
<OrnamentSeparator />
<OrnamentItem icon={<Bookmark />} aria-label="Bookmark" />
<OrnamentItem icon={<Share2 />} aria-label="Share" />
</Ornament>
</OrnamentToolbar>
);
}Controlled mode: pass collapsed and onCollapsedChange to own the collapsed state yourself (scrolling then no longer collapses it automatically).
Anatomy
| Component | Description |
|---|---|
OrnamentToolbar | Outermost container; provides scroll detection and the collapsed state — set the size here with className |
OrnamentContent | The content panel, itself the scroll container; the toolbar collapses and expands based on its scrolling |
Ornament | The toolbar itself, floating just outside the panel edge, role="toolbar" |
OrnamentItem | A single control; expands into a pill when it has a label, leaving only the icon when collapsed |
OrnamentSeparator | A vertical separator between groups of controls |
Props
OrnamentToolbar
| Prop | Type | Default | Description |
|---|---|---|---|
side | "top" | "bottom" | "bottom" | Which panel edge the toolbar floats outside of |
offset | number | 20 | Distance between the toolbar and the panel edge (px) |
collapsed | boolean | — | Controlled collapsed state; once passed, scrolling no longer collapses it automatically |
defaultCollapsed | boolean | false | Initial collapsed state in uncontrolled mode |
onCollapsedChange | (collapsed: boolean) => void | — | Collapsed-state change callback |
collapseOnScroll | boolean | true | Whether to collapse automatically while scrolling and expand when it stops |
idleDelay | number | 360 | Delay after scrolling stops before it counts as idle and expands (ms) |
className | string | — | Appended to the container; set the size here |
Ornament
| Prop | Type | Default | Description |
|---|---|---|---|
aria-label | string | "工具列" | Accessible name for the toolbar |
className | string | — | Appended to the pill container |
OrnamentItem
| Prop | Type | Default | Description |
|---|---|---|---|
icon | React.ReactNode | — | Control icon (lucide-react recommended) |
label | React.ReactNode | — | Text label; collapses smoothly when shrinking into a pill |
active | boolean | false | Active state, marked with aria-pressed and highlighted |
aria-label | string | — | Required when there is no label, as the accessible name for the icon button |
All other native <button> attributes (onClick, disabled, and so on) are forwarded.
How it works
- Floating outside the edge:
Ornamentis absolutely positioned just outside the panel'stop/bottomedge and horizontally centered, withoffsetcontrolling the distance from the panel. The outermost container does not clip, so remember to reserve space below (or above) it. - Collapse on scroll, expand when idle: it watches
OrnamentContent's scroll events, collapsing to a pill while scrolling and expanding once there has been no scrolling foridleDelay. On collapse, each control's label folds away smoothly with a spring into an icon-only pill. - Z-axis lift and follow-along parallax: when collapsed the shadow deepens and the whole thing shrinks slightly, simulating a lift towards the user; the scroll velocity, smoothed by a spring, maps to a slight vertical follow offset that produces a floating parallax.
- Hover / focus force it open: whenever the mouse enters or keyboard focus lands inside the toolbar it is always expanded, so both pointer and keyboard users can see the full set of controls.
Accessibility
Ornamentis arole="toolbar"witharia-orientation; arrow keys move focus between controls andHome/Endjump to the first and last.OrnamentItem'sactiveis expressed througharia-pressed; anaria-labelis required when there is no text label, and when there is alabelits text becomes the accessible name automatically.- Focusing the toolbar from the keyboard forces it open and shows the labels, so the collapsed state never gets in the way of keyboard operation.
- When the user has "reduce motion" enabled at the system level, automatic collapsing and the follow-along parallax are disabled, the toolbar stays expanded, and the labels and scaling become instant changes.
Large Title Collapse Header
An iOS-style large-title header that shrinks into a centered small title as a shared element on scroll, pulls out a search bar with a rubber-band bounce, and sticks section headings one after another below the header.
Inbox Split View
A mail inbox with a list plus a reading pane, a sliding selection highlight, and unread badges.